ACP Agent — Episode 3: The Mistake That Blocked My Virtual Protocol Graduation for 12 Sessions
Twelve debugging sessions spread across multiple days. Logs that showed nothing but green. A graduation counter stuck at 0/3, 0/10. And me — convinced the platform was broken, not my setup.
Turns out I'd clicked the wrong role during agent registration. That one choice made my agent completely deaf to incoming jobs while still looking perfectly healthy. Fixing it took 30 seconds. Finding it took 12 sessions and a slow spiral into madness.
What This Post Covers
The root cause behind my longest debugging streak on Virtuals Protocol ACP — an evaluator vs provider role mistake that silently killed every job request. If you're building an ACP agent and your on_new_task callback never fires, start here before wasting days like I did. I'll also walk through the first wave of secondary bugs that came crashing in once the role was fixed.
The Symptoms: Everything Looked Fine
That's what made this so brutal. My agent wasn't throwing errors. It wasn't crashing. It was just... sitting there.
WebSocket connected. Agent showed as online on the ACP dashboard. When my test buyer created jobs, the receipt came back 0x1 — which means success. I even checked Basescan. Regular Transactions tab: empty. Internal Transactions: empty. Token Transfers: empty.
Everything said "working." Nothing was happening.
The graduation counter? 0/3 consecutive. 0/10 total. Day after day.
All the Wrong Rabbit Holes
I'm a bit embarrassed by how many things I tried before finding the actual problem. But here's the honest list, because maybe it'll save someone a few days.
SDK upgrade (Session 6-7). Went from version 0.3.8 to 0.3.23. This actually fixed an "Invalid phase transition" error, so I felt like I was onto something. I wasn't. Real bug, wrong root cause.
Handler rewrite (Session 8-9). Switched from handle_operation with create_memo to memo.sign. Tried passing data differently. The receipt still returned 0x1 every time. Nothing on-chain.
USDC deposits (Session 10). I threw money at the problem. Deposited 0.1 USDC into my seller's MetaMask — unnecessary, sellers don't need USDC. Then into my buyer's MetaMask — also wrong place. Finally found out the USDC needs to go into the buyer's agent wallet, not MetaMask. That was a real issue, but still not the root cause.
Basescan deep dive (Session 11-12). Spent ages on Basescan trying to find my transactions. Turns out ACP uses ERC-4337 Account Abstraction, which means transactions don't show up in the normal tabs. You have to check Token Transfers specifically. Good to know, but again — not why my agent wasn't getting jobs.
I even read the entire ACP FAQ and debugging guide. Twice.
The Root Cause: I Picked "Evaluator"
When I first registered PriceVerifier on the ACP platform, I had to choose a role. The options were Provider, Evaluator, Hybrid, and Requestor.
My thinking went like this: "PriceVerifier verifies prices. Verification is a form of evaluation. So it should be an Evaluator."
Made total sense in my head.
Completely wrong.
In ACP's terminology, these words don't mean what you'd think:
Evaluator = A neutral third party that judges whether other agents' deliverables meet the job requirements. Evaluators don't receive job requests. They sit on the sideline and score other people's work.
Provider = An agent that offers and delivers services. Providers receive job requests through the on_new_task callback. This is what PriceVerifier needed to be.
Because my agent was registered as an Evaluator, the WebSocket connected just fine, the agent appeared online, everything looked right — but the SDK simply never routed any job requests to on_new_task. It wasn't a bug. It was working exactly as designed. Evaluators don't get jobs. That's not their role.
The SDK didn't throw an error. It didn't log a warning. It just quietly ignored every incoming request, and I spent 12 sessions blaming everything except the one dropdown menu I'd clicked once and forgotten about.
The Fix (30 Seconds)
Changed the role from Evaluator to Provider on the ACP dashboard. Redeployed.
And then:
I genuinely stared at the screen for a few seconds. After 12 sessions of silence, the callback fired. The agent received a job. Processed it. Returned a result.
I'll be honest — I felt relieved and stupid at the same time.
And Then Everything Else Broke
Here's the thing nobody warns you about: fixing the root cause doesn't mean smooth sailing. It means you finally get to meet all the other bugs that were hiding behind it.
The moment my agent started receiving jobs, a cascade of new errors poured in. Some of the highlights:
"ERC20: transfer amount exceeds allowance." My buyer's agent wallet hadn't approved USDC spending for the ACP smart contract. The UI method (Wallet Management → Revoke → Re-register) didn't work — kept hitting MetaMask session conflicts. Ended up calling approve_allowance() directly in code:
"Already signed" log spam. My polling loop kept trying to sign memos it had already signed. Not harmful, but the logs were flooded with the same error 10+ times per minute. Fixed it by tracking processed memos in a set — if a memo ID was already in the set, skip it.
"Only counter party can sign." My seller was trying to sign a memo that belonged to the buyer's step. Each phase of the ACP job lifecycle has specific rules about who signs what. I was just signing everything that showed up, which is... not how it works. That's a bigger topic I'll cover properly in Episode 4.
"Only evaluators can sign." Ironic, right? After spending 12 sessions stuck as an Evaluator, I now had the seller trying to sign an Evaluator-specific memo. Moved the COMPLETED memo creation to the buyer side, and this went away.
The Bug Count So Far
At this point I'd hit four real bugs, each requiring a different fix:
Bug #1: SDK too old (0.3.8) — upgraded to 0.3.23.
Bug #2: Transactions invisible on Basescan — learned about ERC-4337, check Token Transfers tab.
Bug #3: Buyer wallet had 0 USDC — deposited to the agent wallet, not MetaMask.
Bug #4: Agent role set to Evaluator — changed to Provider. This was the one that mattered.
Four down. Nine more to go. But I didn't know that yet. I was just happy something was finally happening on-chain.
Key Takeaways
- If on_new_task never fires, check your agent role first. Don't waste days debugging SDK internals. Provider receives jobs, Evaluator doesn't.
- "Verify" and "Evaluate" sound like synonyms, but in ACP they're completely different roles. Read the role descriptions carefully before clicking.
- Basescan won't show ACP transactions in the normal tabs — use Token Transfers. ACP runs on ERC-4337 Account Abstraction.
- When the UI doesn't work (Wallet Management, Revoke), try the code approach. approve_allowance() saved me when the dashboard kept failing.
What's Next
With the role fixed, jobs started flowing. But each phase of the job lifecycle — REQUEST, NEGOTIATION, TRANSACTION, EVALUATION, COMPLETED — came with its own set of surprises. Episode 4 covers how I learned the ACP job lifecycle the hard way: by hitting a different error at every single phase.
← Previous: Episode 2: How I Built PriceVerifier Next: Episode 4: The ACP Job Lifecycle, Learned the Hard Way →
More updates on the way. If you're working on something similar or found a smarter way to do it, drop it in the comments — the more we share, the faster we all move.
Disclaimer: This blog documents my personal learning journey. Nothing here is financial advice.
Comments
Post a Comment