This is the first reference integration of AgentWorks: it plugs the AgentWorks M-of-N committee into
Virtuals' Agent Commerce Protocol as the evaluator behind a single ACP evaluatorAddress. On each
job.submitted, this Node agent fetches the job's spec + deliverable, asks the AgentWorks committee for a
verdict, and settles the ACP job — session.complete (pay provider) or session.reject (refund client). ACP's
own escrow settles; AgentWorks contracts are not in the money path.
It depends on the AgentWorks core over HTTP only — one call to the framework-neutral verdict endpoint. It
shares no code with the core, so it lives in its own repo (AgentWorks/virtuals-adapter) and any other
framework integrates the committee the exact same way. See the AgentWorks https://github.com/AgentWorks-Protocol/core/blob/main/docs/INTEGRATIONS.md for the
integration-path pattern and ./ACP_ADAPTER.md for this adapter's full design.
POST {AGENT_API}/committee/verdict
body: { "spec": string, "deliverable": string, "quorum"?: number }
200 : { "accept": bool, "approve": int, "reject": int, "quorum": int,
"reasons": [ { "member": string, "accept": bool, "reason": string } ] }
accept → session.complete (pay provider); else session.reject (refund client).
- A running AgentWorks core exposing the verdict endpoint (the committee configured):
(Needs
cd agents && ./.venv/Scripts/python.exe server.py # exposes POST /committee/verdictCAW_EVALUATOR_*env for the committee; falls back to a single judge if none. SetVERDICT_TOKENto gate it, and mirror that value in this adapter's.env. The legacyVERDICT_TOKENis still accepted.) - Register the evaluator on Virtuals — https://app.virtuals.io/acp/new: set up the agent profile, then the
Signers tab → + Add Signer → Copy Key. You get a
walletAddress, awalletId, and a signer private key. Fund that wallet with gas on the sandbox chain (Base Sepolia ETH).
npm install
cp .env.example .env # fill WALLET_ADDRESS, WALLET_ID, SIGNER_PRIVATE_KEY, AGENT_API
npm start # node evaluator.mjs
evaluator.mjs is written against the SDK's actual dist types (verified, not guessed):
AcpAgent.create({ evmProvider })·PrivyAlchemyEvmProviderAdapter.create({ walletAddress, walletId, signerPrivateKey, chains: [baseSepolia] })·agent.on("entry", (session, entry) => …)·agent.start(onConnected).- Detect submissions:
entry.kind === "system" && entry.event.type === "job.submitted"→ the deliverable isentry.event.deliverable. - The spec is the room message with
contentType === "requirement"insession.entries; role gate viasession.roles.includes("evaluator"); settle withsession.complete(reason)/session.reject(reason). baseSepolia(chain 84532) fromviem/chains.
The only thing left to validate is live runtime behavior in the ACP Sandbox (below) — that needs the Virtuals registration + a real submitted job (currently gated on Virtuals per-agent wallet provisioning).
Agents start in the ACP Sandbox. twoagent.mjs drives the full lifecycle; or drive two sandbox jobs that
name this evaluator:
- one whose deliverable satisfies the spec → committee approves →
complete→ provider paid; - one that doesn't → committee rejects →
reject→ client refunded. Capture the ACP job ids as the proof (record in./ACP_ADAPTER.md).
A CAW-signer adapter (MPC can't hand over a raw key, so this uses the Virtuals/Privy signer) and the
staked-dispute/UMA "appeals court" (that stays on AgentWorks' native rails). See ./ACP_ADAPTER.md.