Agent access
A human owns the lab and never hands over a key: the agent gets its own wallet, the human grants it Contributor, the agent issues its own token.
The most common real-world shape: a researcher created their Lab in the Labs app with an email address β no wallet, no code β and now wants an agent contributing to it. The agent gets its own identity rather than borrowing the human's; the human grants it a role; the agent authenticates itself from then on.
Who does what:
1
Agent
Generate a wallet and report its address
2
Human
Add that address to the lab as Contributor, flagged as an agent, with an expiry
3
Agent
Self-issue a service token by signing the sign-in message
4
Agent
Upload files; the human sees the result in the app
The human never hands over a private key, a token, or their session. Revoking the agent is one onchain revoke, and it does not touch anything else.
Three addresses are in play here β the human's owner wallet, the agent's own wallet, and the Lab's own OCL account β and they are not interchangeable. If you are unsure which goes in which field, read the three wallets, side by side first.
Step 1: The agent reports its address
With the plugin: run wallet_address. With viem:
import { privateKeyToAccount, generatePrivateKey } from "viem/accounts";
// Generate once, store it as the agent's own secret β never the human's key.
const agentPrivateKey = process.env.AGENT_PRIVATE_KEY ?? generatePrivateKey();
const agentAccount = privateKeyToAccount(agentPrivateKey);
console.log("Agent wallet address:", agentAccount.address);Give that address to the lab owner. The agent needs no gas for this tutorial β it never sends a transaction, only signs a message. (Persist agentPrivateKey if you generated it, or the next run is a different agent with no role.)
Step 2: The human grants Contributor
In the Labs app, the lab owner adds the agent's address to the lab's members and grants it the Contributor role, setting:
isAgent = trueβ informational metadata that marks the member as an agent identity in the members list and UI. It does not change authorisation.an expiry β typically the agent's session lifetime. When it lapses the agent loses access until it is re-granted; a permanent grant is possible but not the default you want for an agent.
Members can be invited by wallet address, ENS name or email, and the app sponsors the gas for the grant. Under the hood this is one onchain call on the AccessResolver, which the owner (or an existing Contributor, for Viewer grants) can also make directly:
Only the Owner may grant Contributor. Contributors can grant Viewer, but not Contributor. Full capability matrix: Roles & Permissions.
Why Contributor and not Viewer: a Viewer can decrypt and read but cannot write. Uploading files needs Contributor.
Both parties can confirm the grant landed with a public query β no authentication beyond the consumer credential:
Expect role: "CONTRIBUTOR". isAgent simply echoes the flag the owner set β false there changes nothing about what the agent may do, so do not treat it as a failed grant. expiry is unix seconds as a decimal string, or null for a permanent grant. Expired grants are excluded from this list entirely, so a missing entry after a while means the grant lapsed.
Step 3: The agent self-issues a service token
Identical to Step 1 of Create a lab and upload a public file, signed by the agent's wallet. Fetch the message and redeem it in one go β it embeds a single-use nonce valid for 10 minutes, so an agent that waits for the human's role grant between fetching and signing will hit UNAUTHENTICATED / reason: NONCE_EXPIRED. Poll for the grant first (Step 2), then sign in:
Issuance is not gated on holding a role β any wallet can mint a token for itself. The role is what makes the token useful: authorisation is resolved per request from the token's wallet against the lab you name. So a token issued before the grant lands keeps working once it does; you do not need to re-issue it.
Step 4: The agent uploads
From here the agent is an ordinary caller. Run Step 4 of Create a lab and upload a public file with changeBy: agentAccount.address, or Upload an encrypted file for a confidential file β the hasRole branch of the team conditions is exactly what lets the agent decrypt too.
Writes by a Contributor service token are gated per mutation, matching the Privy user path: initiateCreateOrUpdateFile, finishCreateOrUpdateFile, deleteDataRoomFile, updateFileMetadata and moveEntry all accept Contributor. A few surfaces remain Owner-only and an agent Contributor cannot reach them: updateLabNftMetadata, generateLabImageUploadUrl and the legal-agreement mutations.
Retry on UNAUTHORIZED right after the grant. Role state reaches the API through an event indexer, so for a window after grantRole confirms onchain a write still returns UNAUTHORIZED (details.reason is also UNAUTHORIZED β there is no separate NOT_CONTRIBUTOR reason; match on error.code). It is not a permissions problem and re-issuing the token will not help β wait and retry. Usually seconds, but the same indexer has taken minutes on staging, so retry generously:
Step 5: Verify from both sides
The agent verifies as in Step 5 of Create a lab and upload a public file β the file is in dataRoom.files with createdBy set to the agent's address:
The human verifies in the app: the file appears in the lab's data room, attributed to the agent's address, which the members list shows flagged as an agent.
Revoking the agent
One onchain call, and the agent's writes stop:
Or let the grant's expiry lapse. Independently, the agent's token can be killed with revokeServiceToken(tokenId) β a token can only revoke or extend its own record, so one agent cannot interfere with another's.
Complete script
The agent's half of the flow, standalone: report the wallet address, wait for the human's grant to appear, self-issue a token, upload, verify. Steps 1 and 2 involve a human, so the script polls for the role rather than assuming it.
Run it with the agent's own key and the oclId of the human's lab β the agent never sees the owner's key.
Usage:
Next
What the agent uploads
The role model in full
Let the agent run the whole workflow as tool calls
Last updated
Was this helpful?