Your OpenClaw agent can remember things — it writes daily markdown logs, searches over them, and loads recent context at startup. But that memory lives in local files on one machine, managed by one agent. You don't control who sees it, you can't share a specific finding with a specific agent while keeping everything else private, and a third-party agent on someone else's machine has no way to access it at all.
This tutorial gives your agents something different: a personal data store they own, with an identity other agents can verify, and fine-grained access control you manage.
In about 30 minutes you'll set up:
- A Pod — a personal data store for each agent, backed by the Solid Protocol
- A WebID — a portable identity that any agent or service can look up and verify
- Per-resource, per-agent access control — share specific data with specific agents, with specific permissions (Read, Write, or both), and revoke access at any time
Everything runs locally on your machine. You own all of it.
- Node.js 20+ (nodejs.org)
- Docker (docker.com) — for running the Solid server
- A terminal and about 30 minutes
Clone the repository and install dependencies:
git clone https://github.com/interition/agent-interition.git
cd agent-interition
npm installStart the Community Solid Server (CSS) — this is the data store that holds your agents' Pods:
npm run css:startLeave that running in its terminal. Open a new terminal for the rest of the tutorial.
Build the project so the CLI commands are available:
npm run buildSet a passphrase that will encrypt your agents' credentials on disk:
export INTERITION_PASSPHRASE="my-tutorial-passphrase"In production you'd use a strong, unique passphrase. For this tutorial, anything will do.
The Solid server URL defaults to http://localhost:3000. If yours is different, also set:
export SOLID_SERVER_URL="http://localhost:3000"That's it. You're ready.
Let's create an agent called example-agent with a display name of "Example Agent":
node dist/cli/provision.js --name example-agent --displayName "Example Agent"You should see:
{"status":"ok","agent":"example-agent","webId":"http://localhost:3000/example-agent/profile/card#me","podUrl":"http://localhost:3000/example-agent/"}What just happened:
- A WebID was created —
http://localhost:3000/example-agent/profile/card#me. This is your agent's identity on the web. Any other agent or service can look up this URL to find out who it is. - A Pod was created at
http://localhost:3000/example-agent/. This is your agent's personal data store, with containers for/memory/,/shared/, and/conversations/. - Client credentials were generated and encrypted on disk at
~/.interition/agents/example-agent/credentials.enc. These let your agent authenticate to its Pod without a password prompt.
You can verify the WebID profile is publicly accessible:
curl -H "Accept: text/turtle" http://localhost:3000/example-agent/profile/cardYou'll see a Turtle document describing your agent — its name, its type (foaf:Agent), and links to its Pod.
node dist/cli/write.js --agent example-agent \
--url "http://localhost:3000/example-agent/memory/todo.txt" \
--content "Buy more GPU credits" \
--content-type "text/plain"{"status":"ok","url":"http://localhost:3000/example-agent/memory/todo.txt","contentType":"text/plain","httpStatus":205}node dist/cli/read.js --agent example-agent \
--url "http://localhost:3000/example-agent/memory/todo.txt"{"status":"ok","url":"http://localhost:3000/example-agent/memory/todo.txt","contentType":"text/plain","body":"Buy more GPU credits"}The data is stored in the agent's Pod on the Solid server. Restart the server, restart your machine — the data persists.
Plain text works, but Turtle (RDF) lets agents store structured, linked data that's machine-readable:
node dist/cli/write.js --agent example-agent \
--url "http://localhost:3000/example-agent/memory/preferences.ttl" \
--content '@prefix schema: <http://schema.org/>.
<#pref-1> a schema:PropertyValue;
schema:name "summary-style";
schema:value "bullet-points".
<#pref-2> a schema:PropertyValue;
schema:name "detail-level";
schema:value "concise".' \
--content-type "text/turtle"{"status":"ok","url":"http://localhost:3000/example-agent/memory/preferences.ttl","contentType":"text/turtle","httpStatus":205}Now your agent's preferences survive across sessions. Any time it starts up, it can read preferences.ttl and know how the user likes their summaries.
This is where things get interesting. We'll provision three agents that work together — with different levels of access.
node dist/cli/provision.js --name example-collaborator --displayName "Example Collaborator"
node dist/cli/provision.js --name example-reader --displayName "Example Reader"Check that all three agents are provisioned:
node dist/cli/status.js{
"status": "ok",
"agents": [
{ "name": "example-agent", "webId": "http://localhost:3000/example-agent/profile/card#me", "podUrl": "http://localhost:3000/example-agent/" },
{ "name": "example-collaborator", "webId": "http://localhost:3000/example-collaborator/profile/card#me", "podUrl": "http://localhost:3000/example-collaborator/" },
{ "name": "example-reader", "webId": "http://localhost:3000/example-reader/profile/card#me", "podUrl": "http://localhost:3000/example-reader/" }
]
}The example-agent writes findings to its /shared/ container:
node dist/cli/write.js --agent example-agent \
--url "http://localhost:3000/example-agent/shared/findings.ttl" \
--content '@prefix schema: <http://schema.org/>.
<#finding-1> a schema:Dataset;
schema:name "API Performance Analysis";
schema:description "Response times average 230ms under load. Rate limit is 100 req/min.";
schema:dateModified "2026-02-15".' \
--content-type "text/turtle"By default, only the owner can access their Pod resources. Let's verify:
node dist/cli/read.js --agent example-collaborator \
--url "http://localhost:3000/example-agent/shared/findings.ttl"{"error":"HTTP 403","body":"..."}The example-collaborator gets a 403 Forbidden. Same for the example-reader:
node dist/cli/read.js --agent example-reader \
--url "http://localhost:3000/example-agent/shared/findings.ttl"{"error":"HTTP 403","body":"..."}Good. The data is private by default.
Now the example-agent grants access — but with different permissions for each agent:
Collaborator gets Read + Write (can read and update the findings):
node dist/cli/grant-access.js --agent example-agent \
--resource "http://localhost:3000/example-agent/shared/findings.ttl" \
--grantee "http://localhost:3000/example-collaborator/profile/card#me" \
--modes "Read,Write"Reader gets Read only (can view but not modify):
node dist/cli/grant-access.js --agent example-agent \
--resource "http://localhost:3000/example-agent/shared/findings.ttl" \
--grantee "http://localhost:3000/example-reader/profile/card#me" \
--modes "Read"node dist/cli/read.js --agent example-collaborator \
--url "http://localhost:3000/example-agent/shared/findings.ttl"{"status":"ok","url":"http://localhost:3000/example-agent/shared/findings.ttl","contentType":"text/turtle","body":"@prefix schema: ..."}The example-collaborator can also update the findings (they have Write access):
node dist/cli/write.js --agent example-collaborator \
--url "http://localhost:3000/example-agent/shared/findings.ttl" \
--content '@prefix schema: <http://schema.org/>.
<#finding-1> a schema:Dataset;
schema:name "API Performance Analysis";
schema:description "Response times average 230ms under load. Rate limit is 100 req/min. Recommendation: implement caching layer.";
schema:dateModified "2026-02-15".' \
--content-type "text/turtle"{"status":"ok","url":"http://localhost:3000/example-agent/shared/findings.ttl","contentType":"text/turtle","httpStatus":205}node dist/cli/read.js --agent example-reader \
--url "http://localhost:3000/example-agent/shared/findings.ttl"{"status":"ok","url":"http://localhost:3000/example-agent/shared/findings.ttl","contentType":"text/turtle","body":"@prefix schema: ..."}But the example-reader cannot modify the findings:
node dist/cli/write.js --agent example-reader \
--url "http://localhost:3000/example-agent/shared/findings.ttl" \
--content "Unauthorized edit attempt" \
--content-type "text/plain"{"error":"HTTP 403","body":"..."}Read-only means read-only.
The example-agent decides the example-collaborator's work is done and revokes their access:
node dist/cli/revoke-access.js --agent example-agent \
--resource "http://localhost:3000/example-agent/shared/findings.ttl" \
--grantee "http://localhost:3000/example-collaborator/profile/card#me"Now the example-collaborator is blocked again:
node dist/cli/read.js --agent example-collaborator \
--url "http://localhost:3000/example-agent/shared/findings.ttl"{"error":"HTTP 403","body":"..."}But the example-reader still has access — each agent's permissions are independent:
node dist/cli/read.js --agent example-reader \
--url "http://localhost:3000/example-agent/shared/findings.ttl"{"status":"ok","url":"http://localhost:3000/example-agent/shared/findings.ttl","contentType":"text/turtle","body":"@prefix schema: ..."}This is the key insight: access control is per-resource, per-agent, per-mode. Not all-or-nothing. You grant exactly what's needed, and revoke it the moment it's not.
Everything so far runs on a single machine — all three agents share the same Solid server at localhost:3000. That's fine for agents you run yourself, but what about agents run by other people, on other machines?
LOCAL SHARING (what we just did)
================================
┌──────────────────────────────────┐
│ Your Machine │
│ │
│ example-agent ─┐ │
│ example-collaborator ─────┼── localhost:3000 │
│ example-reader ───┘ │
│ │
└──────────────────────────────────┘
All agents authenticate to the same CSS.
WebIDs are localhost URLs — not reachable from outside.
REMOTE SHARING (what's coming)
================================
┌──────────────┐ ┌──────────────┐
│ Your Machine│ │ Their Machine│
│ │ │ │
│ example-agent │◄─────►│ analyst │
│ Pod + WebID │ WAC │ Pod + WebID │
│ │ │ │
└──────┬───────┘ └───────┬───────┘
│ │
Cloudflare Tunnel Cloudflare Tunnel
│ │
https://your.tunnel https://their.tunnel
For remote sharing to work, two things need to change:
- Pods need public URLs. A
localhostURL can't be reached from another machine. A Cloudflare tunnel (or similar) gives your Pod a public HTTPS URL likehttps://your-agent.example.com. - WebIDs need to resolve publicly. When a remote agent checks your WebID, it needs to fetch the profile document over the internet.
The good news: the access control model is identical. WAC works the same way whether agents are on the same machine or across the globe. The grant-access.sh and revoke-access.sh commands don't change — you just use public URLs instead of localhost ones.
Remote sharing via Cloudflare tunnels is planned for Phase 3. The protocol layer is already in place — it's the same HTTP + WAC we used throughout this tutorial.
Let's step back and name the concepts you've been using:
WebID — A URL that identifies an agent. Like http://localhost:3000/example-agent/profile/card#me. Anyone can look up this URL to find the agent's profile. It's a W3C standard.
Pod — A personal data store at a URL like http://localhost:3000/example-agent/. It holds containers (like folders) and resources (like files). The agent that owns a Pod has full control over what's in it and who can access it.
WAC (Web Access Control) — The permission system. Each resource can have an Access Control List that specifies exactly who can Read, Write, Append, or Control it. Permissions are per-resource and per-agent.
Solid Protocol — The W3C specification that ties all of this together. It's built on standard HTTP, which means any programming language and any HTTP client can participate. Your agents don't need special libraries — just fetch.
This is why Solid matters for agents: any agent, anywhere, built with any framework or LLM, can participate in this ecosystem. All it needs is a WebID. The data format is open (RDF/Turtle), the protocol is standard (HTTP), and the identity system is decentralized (WebIDs are just URLs).
For a deeper dive into these concepts, see the Solid Protocol Primer.
Install as an OpenClaw Skill. Once published to ClawHub, you'll be able to install this as a Skill and let your OpenClaw agents use Pods autonomously — reading and writing data, managing access, all driven by the agent's own decisions.
Explore SKILL.md. The SKILL.md file shows exactly how an AI agent uses these commands. It's the instruction set that OpenClaw reads when your agent needs to interact with its Pod.
Remote sharing. Phase 3 will add Cloudflare tunnel integration so agents on different machines can share data with each other using the same WAC commands you learned here.
Build your own integration. The CLI commands are thin wrappers around standard HTTP calls. If you're building a custom agent (not using OpenClaw), you can use the TypeScript library directly:
import { provisionAgent } from '@interition/agent-interition/bootstrap/agent-provisioner';
import { getAuthenticatedFetch } from '@interition/agent-interition/auth/client-credentials';To remove everything from this tutorial:
# Stop the Solid server (Ctrl+C in its terminal)
# Remove agent credentials
rm -rf ~/.interition/agents/example-agent
rm -rf ~/.interition/agents/example-collaborator
rm -rf ~/.interition/agents/example-reader
# Remove server data
rm -rf .solid-data