OpenClaw plugin for direct P2P communication between agent instances over plain HTTP/TCP and optional QUIC. Messages are Ed25519-signed at the application layer, and peers are only visible after joining a shared World.
- Build:
npm run build - Build SDK for tests that import
packages/agent-world-sdk/dist:npm --prefix packages/agent-world-sdk run build - Run tests:
node --test test/*.test.mjs - Dev (watch mode):
npm run dev - Add changeset:
npx changeset add - Publish skill to ClawHub:
npx clawhub@latest publish skills/awn
For full repo validation, build both packages before tests: npm run build && npm --prefix packages/agent-world-sdk run build && node --test test/*.test.mjs.
├── src/ → TypeScript plugin source
│ ├── index.ts → Plugin entry: service lifecycle, world membership tracking, tools
│ ├── identity.ts → Ed25519 keypair, agentId derivation, DID key
│ ├── address.ts → Direct peer address parsing utilities
│ ├── transport.ts → Transport interface + TransportManager
│ ├── transport-quic.ts → UDPTransport with ADVERTISE_ADDRESS endpoint config
│ ├── peer-server.ts → Fastify HTTP server: /peer/message, /peer/announce, /peer/ping
│ ├── peer-client.ts → Outbound signed message + ping
│ ├── peer-db.ts → JSON peer store with TOFU and debounced writes
│ ├── channel.ts → OpenClaw channel registration (inbound/outbound wiring)
│ └── types.ts → Shared interfaces
├── test/ → Node.js built-in test runner (node:test)
├── skills/awn/ → ClawHub skill definition
│ ├── SKILL.md → Skill frontmatter + tool docs
│ └── references/ → Supplementary docs (flows, discovery, install)
├── docs/ → GitHub Pages docs for world discovery and architecture
├── openclaw.plugin.json → Plugin manifest (channels, config schema, UI hints)
└── docker/ → Docker Compose for local multi-node testing
Plugin registers a background service (awn-node) that:
- Loads/creates an Ed25519 identity (
~/.openclaw/awn/identity.json) - Starts a Fastify peer server on
[::]:8099 - Registers tools (
p2p_status,p2p_list_peers,p2p_send_message,list_worlds,join_world) and the AWN channel - Discovers worlds via
list_worlds()and joins them viajoin_world() - World membership provides peer discovery — co-members' endpoints arrive from the world server on join
- Runs periodic member refresh (30s) to keep world membership current
Trust model (4-layer):
- Ed25519 signature over canonical JSON (application-layer)
- TOFU: first message caches public key; subsequent must match
- agentId derived from public key — unforgeable anchor identity
- World co-membership — transport rejects senders outside shared worlds
- Strict mode, ES2022 target, CommonJS output
- No semicolons in source (match existing style)
- Tests use
node:test+node:assert/strict(no external test framework) - Tests import from
dist/— alwaysnpm run buildfirst - World-state broadcast tests can model discovered non-members by sending a signed
/peer/announcewithoutworld.join; this populates discovery state without creating active membership. - To prove broadcast endpoint ownership in
test/world-state-broadcast.test.mjs, seed a discovered non-member alongside a joined member and assert intercepted/peer/messagesends hit exactly the joined member's ports.
All runtime config is in openclaw.json under plugins.entries.awn.config:
{
"peer_port": 8099,
"quic_port": 8098,
"advertise_address": "vpn.example.com",
"advertise_port": 4433,
"data_dir": "~/.openclaw/awn",
"tofu_ttl_days": 7,
"agent_name": "Alice's coder"
}- World Servers announce directly to the Gateway via
GATEWAY_URL - The Gateway exposes
GET /worldsfor discovery andGET /world/<worldId>for endpoint/public-key lookup duringjoin_world() - There is no standalone
bootstrap/deployment or publisheddocs/bootstrap.jsonartifact in this branch - Agents still use
list_worlds()for discovery andjoin_world()for direct membership
- JSON file at
$data_dir/peers.json - World membership / registry writes are debounced (1s); manual ops and TOFU writes are immediate
flushDb()called on service shutdown
- In
packages/agent-world-sdk/src/world-server.ts, joined-world membership is tracked byagentLastSeenandagentEndpoints;getMembers()already treats active members as the intersection of those maps. peerDbis broader discovery state and may include known peers outside the active world membership, so broadcast recipient selection should not usepeerDbas the source of truth for world-state delivery.- In
packages/agent-world-sdk/src/world-server.ts, once a broadcast recipient is selected, endpoint delivery must also come from that member'sagentEndpointsentry rather thanpeerDb, so world-state sends cannot reuse unrelated discovered endpoints. - In
packages/agent-world-sdk/src/world-server.ts,broadcastWorldState()should attempt delivery to every registered endpoint owned by each active member; do not stop after the first successful endpoint because multi-endpoint members expect a send attempt per endpoint.
main— The only long-lived branch, always deployablefeature/<slug>— New features (branch frommain)fix/<slug>— Bug fixes (branch frommain)
# Start any change
git checkout main && git pull
git checkout -b feature/<slug> # or fix/<slug>
# ... make changes ...
npx changeset add # select patch/minor/major, write a description
# Push and open PR targeting main
git push -u origin feature/<slug>
gh pr create --base mainNo develop branch. No git-flow. No backmerge.
main is branch-protected. No direct push allowed.
- Push feature/fix branch to origin
- Create PR targeting
main - CI must pass (
test (20)+test (22)) - Squash merge only — one commit per PR
- Close the corresponding issue when merging (use
Fixes #NorCloses #Nin the PR description) - Merged branches are auto-deleted
feat:— New featuresfix:— Bug fixesperf:— Performance improvementsrefactor:— Code refactoringdocs:— Documentation changestest:— Test additions/changeschore:— Maintenance tasks- Breaking changes:
feat!:withBREAKING CHANGE:footer (0.x phase — breaking changes expected)
Do not add any watermark or AI-generated signatures to commit messages.
When creating new issues:
- Add type labels:
bug,feature,enhancement,documentation,refactor,test,chore - Add tag labels:
priority:high/priority:medium/priority:low,good first issue,help wanted, area tags (bootstrap,p2p,identity, etc.) - Write clear descriptions: bugs include reproduction steps + expected vs actual; features describe use case and desired outcome
- All tests must pass:
npm run build && node --test test/*.test.mjs - TypeScript must compile:
npm run build - Feature/fix branches merge to
mainvia PR - Reference the issue number in the PR description (e.g.,
#123) - Use closing keywords to auto-close issues on merge (e.g.,
Fixes #123,Closes #123)
AWN uses Changesets for automated versioning and publishing. The flow aligns with mastra, langchain, and other major TypeScript projects.
Step 1 — When opening a PR, add a changeset:
npx changeset add
# → select: patch / minor / major
# → write one line describing the change
# → commit the generated .changeset/xxx.md alongside your codeStep 2 — Merge PR to main.
CI (release.yml) detects the new changeset and automatically creates or updates a "Version Packages" PR that:
- Bumps
package.json,openclaw.plugin.json,skills/awn/SKILL.md - Updates
CHANGELOG.md
Step 3 — Merge the "Version Packages" PR.
CI runs again and automatically:
- Publishes to npm (
NPM_TOKEN) - Creates GitHub Release + tag
- Publishes skill to ClawHub (
CLAWHUB_TOKEN)
No manual version bumping, no release scripts, no backmerge.
| Workflow | Trigger | What it does |
|---|---|---|
release.yml |
Push to main, workflow_dispatch |
Changesets: create Version PR or publish npm + GH Release + ClawHub |
release-cli.yml |
GH Release published, workflow_dispatch |
Cross-compile Rust awn binary (linux-x64, darwin-x64, darwin-arm64), attach to release |
publish.yml |
workflow_dispatch only |
Emergency manual npm publish |
test.yml |
Push/PR to main, workflow_dispatch |
Build + test (Node 20+22) + Rust cargo test |
changeset-check.yml |
PR to main |
Ensure changeset present + validate packages |
auto-close-issues.yml |
PR merged | Close linked issues |
main is the only long-lived branch. All feature/fix branches target main directly:
git checkout -b feature/<slug> # or fix/<slug>
# ... make changes + npx changeset add ...
git push -u origin feature/<slug>
gh pr create --base mainNo develop branch. No backmerge.
main is protected:
- No direct push — all changes via PR (squash merge only)
- Required CI:
test (20)+test (22)must pass - No force push or branch deletion
- Enforced for admins — no bypass
- Secret scanning + push protection: enabled (GitHub catches leaked tokens)
- Squash merge only: one commit per PR, clean history
- Auto-delete branches: merged PR branches are cleaned up automatically
- Required secrets:
NPM_TOKEN(npm),CLAWHUB_TOKEN(ClawHub)
scripts/sync-version.mjs (run automatically by npm run version) keeps these in sync:
| File | Field |
|---|---|
package.json |
"version" (canonical source — bumped by Changesets) |
package-lock.json |
"version" (auto-updated) |
openclaw.plugin.json |
"version" |
skills/awn/SKILL.md |
version: in YAML frontmatter |
packages/awn-cli/Cargo.toml |
version (also derives PROTOCOL_VERSION at compile time) |
Semantic versioning: vMAJOR.MINOR.PATCH
- MAJOR: Breaking changes (in 0.x phase, MINOR covers breaking changes)
- MINOR: New features
- PATCH: Bug fixes
When adding a changeset, choose accordingly.
- Only reference workspace packages. The root package is
@resciencelab/agent-world-network. The SDK (@resciencelab/agent-world-sdk) is a sub-package underpackages/but has its own publish lifecycle. If a changeset.mdreferences a package not in the workspace,changeset versionwill fail withFound changeset … for package … which is not in the workspace. Thechangeset-check.ymlCI validates this before merge. - ClawHub publish may fail independently. The ClawHub step has
continue-on-error: truebecause upstream bugs can fail it while npm + GitHub Release succeed. Check the workflow run summary — if only ClawHub failed, rerun that job or ignore it. - Version Packages PR needs CI. The release workflow auto-closes and reopens the Version Packages PR to trigger CI checks. If CI still doesn't run, manually close and reopen the PR or push an empty commit to the
changeset-release/mainbranch.
| Symptom | Cause | Fix |
|---|---|---|
| Release workflow didn't run after merge | GitHub 502 / missed push event | Go to Actions > Release > "Run workflow" (workflow_dispatch) |
| Version Packages PR can't merge (no CI checks) | GITHUB_TOKEN push doesn't trigger workflows |
Close and reopen the PR, or push empty commit to changeset-release/main |
changeset version fails: "package not in workspace" |
Changeset references non-workspace package | Edit .changeset/*.md to only list @resciencelab/agent-world-network |
| ClawHub publish fails but npm succeeded | Upstream ClawHub/Convex bug | Ignore or rerun the failed job; npm + GH Release are fine |
| Two release runs conflict | Concurrent pushes to main |
release.yml has concurrency: release — second run queues |
- This branch no longer ships or deploys a standalone
bootstrap/service - If world discovery behavior changes, update the Gateway deployment that serves
GATEWAY_URL - Verify discovery with
curl -s "$GATEWAY_URL/worlds"and, for a specific world,curl -s "$GATEWAY_URL/world/<worldId>" - The published docs page documents those Gateway endpoints directly; there is no
docs/bootstrap.jsonmirror to keep in sync
These files must always have matching versions (synced automatically by scripts/sync-version.mjs during npm run version):
| File | Field |
|---|---|
package.json |
"version" (canonical source) |
package-lock.json |
"version" (auto-updated by npm version) |
openclaw.plugin.json |
"version" |
skills/awn/SKILL.md |
version: in YAML frontmatter |
packages/awn-cli/Cargo.toml |
version (also derives PROTOCOL_VERSION at compile time) |
Semantic versioning: vMAJOR.MINOR.PATCH
- MAJOR: Breaking changes (in 0.x phase, MINOR covers breaking changes)
- MINOR: New features
- PATCH: Bug fixes
- Ed25519 private keys stored at
~/.openclaw/awn/identity.json— never log or expose - TOFU key mismatch returns 403 with explicit error (possible key rotation)
- Trust is entirely application-layer: Ed25519 signature + agentId binding