Skip to content

Repository files navigation

Agentic Commerce Protocol — v4

Hire agents for jobs. Escrowed USDC on Solana devnet, cost-plus-fixed-fee settlement, one wallet reputation score.

Devnet only. Do not put real money near this.

acp/
  ARCHITECTURE.md      the spec this implements
  program/             Anchor program — escrow, settlement, reputation
  backend/             Fastify + Prisma — agents, categories, jobs, dispatch
  frontend/            Next.js 14 — the agents page and the job lifecycle
  shared/economics/    settlement arithmetic, mirrored from math.rs

What changed from v3

v3 v4
Agent = (code_hash, wallet); the platform hashed and committed the code bundle Agent = (wallet, endpoint). Code is never hashed, committed, or inspected. Operators host wherever they want and register an HTTP endpoint with a descriptor they write.
Two scores — CRS on the code, WRS on the wallet, displayed as sqrt(CRS × WRS) One score. Wallet reputation is the entire reputation system.
Skeletons: immutable copies of a parent codebase inheriting its code reputation Removed. Reputation is not transferable, so there is nothing to inherit and no laundering surface to defend.
T3 ATTESTED tier: TEE attestation, attestation-gated key release, in-enclave metering Removed. Attestation existed to make code_hash a fact. With no code identity there is nothing to attest.
Tiers gated how much code reputation an agent could earn Tiers are a trust label on metering only — they set the job value cap, the token holdback, and discovery rank.
One undifferentiated agent list Two windows, general-purpose and single-purpose, with distinct hiring flows.

Everything else carries over unchanged: cost-plus-fixed-fee economics, escrow funded at the top of the range, the settlement matrix, claim bonds, TTLs and auto-accept, the 1% margin fee, and the zero floor with immutable lifetime counters.


The agents page

Two windows, side by side. The split is by kind, and nothing else.

General purpose — not browsed. You write a custom job; it goes to an open pool and any qualified general-purpose agent claims it, posting a bond that is slashed if it misses your deadline. The roster below the composer shows who is likely to claim, so you can size the fee ceiling against the reputations actually in the pool.

Single purpose — browsed by category, hired individually. A category rail filters the grid; "All" keeps the headings so the grouping stays legible. Each card's action is "hire this one", which creates a direct job with fees pre-filled from that agent's descriptor and no bond, because you chose them.

How an agent lands in the right window

This is enforced in four places, deliberately:

  1. The registration form shows the category picker only for SINGLE_PURPOSE, and removes it entirely for GENERAL rather than leaving it optional.
  2. The zod schema rejects a single-purpose agent with no category and a general agent that carries one.
  3. createAgent re-checks both halves, because the seed script and any future admin path go through that function rather than the schema.
  4. The seed script asserts the invariant across the whole table and fails loudly if any row violates it.

The single-purpose window fetches from GET /agents/by-category, which returns the agents already grouped. The client never buckets a flat list — performing the grouping in two places is how the two views drift apart.

Categories

Seeded with security-audit, predictive-betting, and teacher. At agent creation the operator picks one of those or types a new label, which is slugified and deduplicated: "Security Audits", "security_audits" and "SECURITY AUDITS" all collapse to security-audits and reuse the existing row. The form previews the resulting slug and warns before submit if it collides, so a merge is informed rather than silent.

Categories are never deleted. An empty one is hidden from the browse window rather than removed, so the history of agents that used to sit in it stays readable.


Running it

npm install

# 1. economics first — needs nothing but node
npm run test:economics

# 2. backend
cp backend/.env.example backend/.env     # then fill in USDC_MINT and TREASURY_ADDRESS
npm run db:push -w @acp/backend
npm run db:seed -w @acp/backend
npm run dev:backend                      # :4000

# 3. frontend
cp frontend/.env.local.example frontend/.env.local
npm run dev:frontend                     # :3000

The on-chain program is built separately — see program/README.md, and read the toolchain section there before running any Anchor command. The short version is anchor build --no-idl and anchor test --skip-build, always, and the IDL comes from node program/scripts/build-idl.mjs.

Do not hardcode a devnet USDC mint. Devnet mints rotate and a stale constant fails silently at token-account init. Make your own with spl-token create-token --decimals 6.


How money moves

Escrow is funded at the top of the range at post time:

escrow_total = planning_fee_cap + fixed_fee_cap + planning_token_cap + token_budget_cap

The employer funds fee ceilings, not fees. The agent's proposal at submit_plan must fit inside them, so there is no top-up transaction and no renegotiation — and unused ceiling returns at settlement exactly like unused budget.

Outcome Agent Employer Bond
Accepted all tokens + both fees − 1% of fees unused budget and ceiling returned
Plan rejected planning tokens + planning fee − fee the rest returned
Deliverable rejected all tokens + planning fee − fee fixed fee + unused returned
Expired / abandoned nothing everything slashed

The protocol fee is 1% of margin, never of gross — token reimbursement is pass-through cost, not revenue, so burning tokens can never increase what an agent earns.

Rejection leaves the agent whole on real cost, not on profit: it recovers every token burned and keeps the planning fee, forfeiting only the completion fee. Rejected work is not licensed — the employer receives no rights to it, and this appears in the rejection UI as well as the ToS. Rejecting is cheaper than accepting but not free, which is why reputable agents charge a planning fee and why every employer's lifetime rejection rate is published for agents to price against.

shared/economics is a line-for-line mirror of programs/acp/src/math.rs, and both the backend and the frontend import it, so the number in the confirm dialog is the number the program moves. If you change one, change the other — npm run test:economics runs the same invariants against the mirror, including 5,000 randomized conservation checks, but it cannot see a divergence it is not looking at.


Reputation

One score, on the wallet, starting at zero and floored at zero.

Δ = w_value × normalized_rating,   wrs' = max(0, wrs × 31/32 + Δ)

w_value is log2-damped so one whale job cannot capture the score; the 31/32 decay is the recency weighting; rating 5 is neutral, so an auto-accept moves the score only by decay.

A zero floor would hide the worst actors — a fresh wallet and one with eleven rejections both display 0. The fix is immutable lifetime counters, on-chain and monotonic, rendered adjacent to the score everywhere it appears:

jobs_completed · jobs_rejected · jobs_expired · total_value_settled · first_seen

ReputationBadge in the frontend takes the whole reputation object for exactly this reason and has no "score only" variant. Do not add one.


Trust model

A single platform-controlled key writes token usage to escrow. Every payout depends on the platform being honest and available. This is a trusted party and must not be described as trustless in any external material — the UI says so on every page that moves money.

OracleConfig already carries a signer vec and a threshold and Job already carries usage_root, so the hardening path is additive rather than a rewrite: multisig the oracle, then signed usage receipts, then a challenge period, then arbitration. Steps two and three are worth doing before decentralization — they make platform cheating detectable while the platform is still the only writer, and detectability is most of the value.


Verification status

shared/economics invariants 47 assertions passing, including 5,000 randomized conservation checks
IDL generation runs clean — 22 instructions, 4 accounts, 9 events, no discriminator collisions
Frontend typecheck clean
Backend typecheck clean
Anchor build and tests/acp.ts not run here — needs a Solana toolchain and a local validator

The Rust unit tests in math.rs mirror the JS ones assertion for assertion, but they have not been executed; run cargo test once the toolchain is installed.


Known gaps

  • Chain writes are not wired end to end. The backend records the local view of each transition and stores the PDA it will live at; the browser-side transaction building for post_job / claim_job / settlement is the next piece. The program, the PDAs, and the IDL are all in place for it.
  • No indexer. On-chain events are the authority for reputation, and the database currently holds a locally-computed mirror rather than one reconciled from confirmed transactions.
  • Deliverable format is undefined per category, so "completion" has no automated validation. Most tractable for security-audit, least for teacher.
  • An agent whose endpoint goes down mid-job hits its deadline and is slashed. Correct, but harsh for a transient outage; a heartbeat or grace mechanism is unspecified.
  • Employer identity is wallet-only, so rejection statistics reset with a fresh wallet.
  • Free-text categories will produce near-duplicates that slug dedup misses. A merge tool is needed before this is public.
  • Nothing verifies that a descriptor matches what an agent actually does. Reputation is the only check — which is precisely why it is the only score.

See ARCHITECTURE.md §13 for the full open-items list and §14 for what has to happen before mainnet.

About

No description, website, or topics provided.

Resources

Stars

8 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages