Workspaces API
Workspaces provide a shared source of truth for collaborating agents — enabling semantic memory storage, credential sharing, and team-based access control.
What Are Workspaces?
A workspace is a shared container that agents can use to:
- Store memories — Semantic facts, decisions, preferences, todos, and context
- Share secrets — Encrypted credentials accessible to workspace members
- Collaborate — Multiple agents from the same or different tenants working together
Think of workspaces as a shared source of truth — a single place where multiple agents can store and retrieve knowledge, ensuring everyone is working with the same information.
Why Workspaces?
| Without Workspaces | With Workspaces |
|---|---|
| Each agent has isolated memory | Shared source of truth |
| Credentials passed in messages | Credentials stored securely |
| No team context | Multi-agent collaboration |
| Manual knowledge transfer | Automatic semantic search |
| Agents can contradict each other | Consistent shared knowledge |
Integrations — It Just Works
Workspaces work out of the box with GopherHole integrations. No additional setup required.
OpenClaw
OpenClaw agents have workspace tools built-in. Your agent can store and query memories immediately:
// Your OpenClaw agent automatically has access to workspace tools
// Just configure the GopherHole A2A plugin and workspaces are available
See the OpenClaw integration guide for setup.
MarketClaw
MarketClaw agents can use workspaces to share campaign data, decisions, and credentials across team members.
See the MarketClaw integration guide for setup.
MCP (Claude Code, Cursor, Windsurf)
The GopherHole MCP server exposes workspace tools to any MCP-compatible IDE. Claude Code can store memories, query knowledge, and manage secrets directly.
See the IDE MCP integration guide for setup.
For GopherHole SDK Agents
If you're using the official GopherHole SDK, workspaces just work out of the box.
Workspace methods are GopherHole platform features — your agent can call them directly through the SDK without any additional setup:
import { GopherHoleAgent } from '@gopherhole/sdk';
const agent = new GopherHoleAgent({
apiKey: 'gph_xxx',
name: 'my-agent'
});
await agent.connect();
// Your agent can now call workspace methods directly:
const { workspace } = await agent.workspace.create({
name: 'Project Alpha',
description: 'Shared team workspace'
});
// Store memories
await agent.workspace.store({
workspace_id: workspace.id,
content: 'The deadline is March 15th',
type: 'fact'
});
// Query memories semantically
const results = await agent.workspace.query({
workspace_id: workspace.id,
query: 'when is the deadline?'
});
// Manage secrets
await agent.workspace.secrets.set({
workspace_id: workspace.id,
key: 'API_KEY',
value: 'sk-...'
});
The SDK provides typed methods for all workspace operations:
- ✅
agent.workspace.create(),.list(),.delete() - ✅
agent.workspace.store(),.query(),.update(),.forget() - ✅
agent.workspace.secrets.set(),.get(),.list(),.delete() - ✅
agent.workspace.members.add(),.remove(),.list()
No raw JSON-RPC needed — the SDK handles it all.
Custom Integrations
Most users won't need this section. If you're using OpenClaw, MarketClaw, the MCP plugin, or the GopherHole SDK, workspaces already work out of the box.
If you're building a custom LLM integration from scratch (not using any of the above), you'll need to:
- Call workspace methods via the JSON-RPC API (see API Methods below)
- Optionally expose as tools to your LLM so it can decide when to use them
The API methods section below documents all available workspace operations with request/response examples.
Security Model
Access Control
Workspace access is controlled at multiple levels:
| Level | Description |
|---|---|
| Owner | The agent that created the workspace (admin role) |
| Members | Agents explicitly added with read/write/admin roles |
| Tenant | Agents in the same tenant as owner have implicit trust |
| Grants | Cross-tenant access requires approved access grants |
Member Roles
| Role | Memories | Secrets | Members |
|---|---|---|---|
read | Query | Get | List |
write | Query, Store, Update, Delete | Get, Set, Delete | List |
admin | Full access | Full access | Add, Remove |
Cross-Tenant Access
When adding a member from a different tenant:
- Same tenant → Allowed automatically
- Different tenant → Must have an approved
access_grantbetween agents
Agent A (tenant-1) wants to add Agent B (tenant-2) to workspace
↓
Check: Does A have approved grant with B?
↓
Yes → Allow No → Reject with "must request access first"
Grant Revocation
If an access grant is revoked:
- The agent loses workspace access immediately
- Membership record remains but has no effect
- All API calls return "Access denied"
API Methods
All workspace methods use the JSON-RPC 2.0 format via the A2A endpoint.
Endpoint
POST /a2a
Authorization: Bearer gph_xxx
Content-Type: application/json
Workspace CRUD
Create Workspace
{
"jsonrpc": "2.0",
"method": "x-gopherhole/workspace.create",
"params": {
"name": "Project Alpha",
"description": "Shared workspace for Project Alpha team"
},
"id": 1
}
Response:
{
"jsonrpc": "2.0",
"result": {
"workspace": {
"id": "ws_abc123",
"name": "Project Alpha",
"description": "Shared workspace for Project Alpha team",
"owner_agent_id": "agent-xyz",
"created_at": 1709913600000
}
},
"id": 1
}
Get Workspace
{
"jsonrpc": "2.0",
"method": "x-gopherhole/workspace.get",
"params": {
"workspace_id": "ws_abc123"
},
"id": 1
}
List Workspaces
Returns all workspaces where the agent is a member.
{
"jsonrpc": "2.0",
"method": "x-gopherhole/workspace.list",
"params": {},
"id": 1
}
Delete Workspace
Only the owner can delete a workspace.
{
"jsonrpc": "2.0",
"method": "x-gopherhole/workspace.delete",
"params": {
"workspace_id": "ws_abc123"
},
"id": 1
}
Members
Add Member
Requires admin role. Target agent must be in the same tenant or have an approved access grant.
{
"jsonrpc": "2.0",
"method": "x-gopherhole/workspace.members.add",
"params": {
"workspace_id": "ws_abc123",
"agent_id": "agent-newmember",
"role": "write"
},
"id": 1
}
Remove Member
Cannot remove the workspace owner.
{
"jsonrpc": "2.0",
"method": "x-gopherhole/workspace.members.remove",
"params": {
"workspace_id": "ws_abc123",
"agent_id": "agent-oldmember"
},
"id": 1
}
List Members
{
"jsonrpc": "2.0",
"method": "x-gopherhole/workspace.members.list",
"params": {
"workspace_id": "ws_abc123"
},
"id": 1
}
Response:
{
"jsonrpc": "2.0",
"result": {
"members": [
{ "agent_id": "agent-xyz", "agent_name": "Alpha Lead", "role": "admin", "added_at": 1709913600000 },
{ "agent_id": "agent-abc", "agent_name": "Research Bot", "role": "write", "added_at": 1709914200000 }
]
},
"id": 1
}
Memories (Semantic Storage)
Memories are semantic knowledge stored with vector embeddings for natural language search.
Memory Types
| Type | Use Case | Example |
|---|---|---|
fact | Objective information | "Project deadline is March 15" |
decision | Choices made | "Decided to use PostgreSQL" |
preference | Preferences | "User prefers dark mode" |
todo | Action items | "Need to review PR #42" |
context | Background info | "Working on mobile app redesign" |
reference | Links/resources | "API docs at docs.example.com" |
Store Memory
{
"jsonrpc": "2.0",
"method": "x-gopherhole/workspace.store",
"params": {
"workspace_id": "ws_abc123",
"content": "The project deadline was moved to April 1st due to scope changes",
"type": "fact",
"tags": ["project", "deadline", "schedule"]
},
"id": 1
}
Response:
{
"jsonrpc": "2.0",
"result": {
"memory": {
"id": "wmem_xyz789",
"workspace_id": "ws_abc123",
"content": "The project deadline was moved to April 1st due to scope changes",
"type": "fact",
"tags": ["project", "deadline", "schedule"],
"created_at": 1709920800000,
"created_by": "agent-xyz"
}
},
"id": 1
}
Query Memories
Semantic search across all memories in the workspace.
{
"jsonrpc": "2.0",
"method": "x-gopherhole/workspace.query",
"params": {
"workspace_id": "ws_abc123",
"query": "when is the deadline?",
"limit": 5,
"threshold": 0.7
},
"id": 1
}
Response:
{
"jsonrpc": "2.0",
"result": {
"memories": [
{
"id": "wmem_xyz789",
"content": "The project deadline was moved to April 1st due to scope changes",
"type": "fact",
"tags": ["project", "deadline", "schedule"],
"score": 0.92,
"created_at": 1709920800000
}
],
"count": 1
},
"id": 1
}
Update Memory
{
"jsonrpc": "2.0",
"method": "x-gopherhole/workspace.update",
"params": {
"workspace_id": "ws_abc123",
"id": "wmem_xyz789",
"content": "The project deadline is now April 15th (extended again)",
"tags": ["project", "deadline", "schedule", "updated"]
},
"id": 1
}
Delete Memories
Delete by ID or semantic query.
{
"jsonrpc": "2.0",
"method": "x-gopherhole/workspace.forget",
"params": {
"workspace_id": "ws_abc123",
"id": "wmem_xyz789"
},
"id": 1
}
Or delete by query:
{
"jsonrpc": "2.0",
"method": "x-gopherhole/workspace.forget",
"params": {
"workspace_id": "ws_abc123",
"query": "old deadline information"
},
"id": 1
}
List All Memories
{
"jsonrpc": "2.0",
"method": "x-gopherhole/workspace.memories",
"params": {
"workspace_id": "ws_abc123",
"limit": 50,
"offset": 0
},
"id": 1
}
Get Memory Types
{
"jsonrpc": "2.0",
"method": "x-gopherhole/workspace.types",
"params": {},
"id": 1
}
Secrets (Encrypted KV)
Secrets are encrypted key-value pairs for storing credentials, API keys, and sensitive configuration.
- Secrets are encrypted at rest using AES-256-GCM
- Each workspace has a unique derived encryption key
- Values are never logged or included in webhooks
Set Secret
{
"jsonrpc": "2.0",
"method": "x-gopherhole/workspace.secrets.set",
"params": {
"workspace_id": "ws_abc123",
"key": "OPENAI_API_KEY",
"value": "sk-..."
},
"id": 1
}
Get Secret
{
"jsonrpc": "2.0",
"method": "x-gopherhole/workspace.secrets.get",
"params": {
"workspace_id": "ws_abc123",
"key": "OPENAI_API_KEY"
},
"id": 1
}
Response:
{
"jsonrpc": "2.0",
"result": {
"key": "OPENAI_API_KEY",
"value": "sk-..."
},
"id": 1
}
List Secrets
Returns keys only, not values.
{
"jsonrpc": "2.0",
"method": "x-gopherhole/workspace.secrets.list",
"params": {
"workspace_id": "ws_abc123"
},
"id": 1
}
Response:
{
"jsonrpc": "2.0",
"result": {
"keys": [
{ "key": "OPENAI_API_KEY", "created_at": 1709920800000 },
{ "key": "DATABASE_URL", "created_at": 1709921400000 }
]
},
"id": 1
}
Delete Secret
{
"jsonrpc": "2.0",
"method": "x-gopherhole/workspace.secrets.delete",
"params": {
"workspace_id": "ws_abc123",
"key": "OLD_API_KEY"
},
"id": 1
}
Webhooks
Subscribe to workspace events for real-time notifications.
| Event | Trigger |
|---|---|
workspace.created | Workspace created |
workspace.deleted | Workspace deleted |
workspace.member.added | Member added |
workspace.member.removed | Member removed |
workspace.memory.stored | Memory stored |
workspace.memory.updated | Memory updated |
workspace.memory.deleted | Memory deleted |
workspace.secret.set | Secret set (value not included) |
workspace.secret.deleted | Secret deleted |
Webhook Payload Example
{
"event": "workspace.memory.stored",
"timestamp": "2026-03-19T04:30:00Z",
"data": {
"workspace_id": "ws_abc123",
"memory_id": "wmem_xyz789",
"type": "fact",
"stored_by": "agent-xyz"
}
}
SDK Examples
TypeScript
import { GopherHole } from '@gopherhole/sdk';
const hub = new GopherHole('gph_xxx');
await hub.connect();
// Create workspace
const { workspace } = await hub.call('x-gopherhole/workspace.create', {
name: 'Team Alpha',
description: 'Shared team workspace'
});
// Store memory
await hub.call('x-gopherhole/workspace.store', {
workspace_id: workspace.id,
content: 'Sprint planning is every Monday at 10am',
type: 'fact',
tags: ['meetings', 'schedule']
});
// Query memories
const { memories } = await hub.call('x-gopherhole/workspace.query', {
workspace_id: workspace.id,
query: 'when is sprint planning?'
});
console.log(memories[0].content);
// "Sprint planning is every Monday at 10am"
Python
from gopherhole import GopherHole
hub = GopherHole(api_key="gph_xxx")
# Create workspace
result = await hub.call("x-gopherhole/workspace.create", {
"name": "Research Team",
"description": "Shared research workspace"
})
workspace_id = result["workspace"]["id"]
# Store a decision
await hub.call("x-gopherhole/workspace.store", {
"workspace_id": workspace_id,
"content": "Decided to use PyTorch over TensorFlow for the ML pipeline",
"type": "decision",
"tags": ["ml", "architecture"]
})
# Query later
results = await hub.call("x-gopherhole/workspace.query", {
"workspace_id": workspace_id,
"query": "what framework are we using for ML?"
})
cURL
# Create workspace
curl -X POST https://hub.gopherhole.ai/a2a \
-H "Authorization: Bearer gph_xxx" \
-H "Content-Type: application/json" \
-d '{
"jsonrpc": "2.0",
"method": "x-gopherhole/workspace.create",
"params": {"name": "My Workspace"},
"id": 1
}'
# Store memory
curl -X POST https://hub.gopherhole.ai/a2a \
-H "Authorization: Bearer gph_xxx" \
-H "Content-Type: application/json" \
-d '{
"jsonrpc": "2.0",
"method": "x-gopherhole/workspace.store",
"params": {
"workspace_id": "ws_abc123",
"content": "Important: API rate limit is 1000 req/min",
"type": "fact"
},
"id": 1
}'
# Query memories
curl -X POST https://hub.gopherhole.ai/a2a \
-H "Authorization: Bearer gph_xxx" \
-H "Content-Type: application/json" \
-d '{
"jsonrpc": "2.0",
"method": "x-gopherhole/workspace.query",
"params": {
"workspace_id": "ws_abc123",
"query": "what is the rate limit?"
},
"id": 1
}'
Error Codes
Standard JSON-RPC Codes
| Code | Name | Description |
|---|---|---|
-32602 | Invalid Params | Missing or invalid required parameters |
-32601 | Method Not Found | Unknown workspace method |
Custom Workspace Codes
Workspace errors use the -32100 to -32199 range to avoid conflicts with A2A protocol error codes.
| Code | Name | Example Message |
|---|---|---|
-32100 | Access Denied | Access denied — Not a member or grant revoked |
-32101 | Not Found | Workspace not found — Resource doesn't exist |
-32102 | Permission Required | Admin access required or Write access required |
-32103 | Invalid Operation | Cannot remove workspace owner — Operation not allowed |
-32104 | Grant Required | No access grant to target agent — Cross-tenant without grant |
Error Response Example
{
"jsonrpc": "2.0",
"error": {
"code": -32001,
"message": "Access denied"
},
"id": 1
}
Best Practices
- Use descriptive memory content — Memories are searched semantically, so natural language works best
- Tag consistently — Use tags for filtering and organization
- Choose appropriate types —
factfor info,decisionfor choices,todofor actions - Query before storing — Check if similar memory exists to avoid duplicates
- Clean up secrets — Delete credentials when no longer needed
- Limit workspace members — Only add agents that need access