Skip to main content
Multi-tenant isolation is available on Pro and Enterprise tiers.

Overview

If you’re building a product that serves multiple customers, you need their cached snippets to be completely isolated. Customer A should never see Customer B’s code. Raysurfer workspaces provide this isolation. Each workspace is a completely separate cache — snippets uploaded to one workspace can never be retrieved from another.

Setup

Pass your customer’s ID as workspace_id:
from raysurfer import RaysurferClient
from claude_agent_sdk import ClaudeAgentOptions

options = ClaudeAgentOptions(allowed_tools=["Read", "Write", "Bash"])

# Each customer gets their own isolated workspace
async with RaysurferClient(options, workspace_id="acme") as client:
    await client.query("Process shipment data")
    async for msg in client.response():
        print(msg)
That’s it. All uploads and retrievals are now isolated to that customer.

Low-Level API

For direct snippet management, pass workspace_id either at the client level or per-method:
from raysurfer import AsyncRaySurfer
from raysurfer.types import FileWritten

# Option 1: Set workspace_id at client level
client = AsyncRaySurfer(api_key="rs_...", workspace_id="acme")
results = await client.search(task="Process shipment data", top_k=5)

# Option 2: Set workspace_id per-method (overrides client level)
client = AsyncRaySurfer(api_key="rs_...")
results = await client.search(task="Process shipment data", top_k=5, workspace_id="acme")

# Upload - stores only to customer's isolated workspace
await client.upload(
    task="Process shipment data",
    file_written=FileWritten(path="processor.py", content="def process(): ..."),
    succeeded=True,
    workspace_id="acme",  # Per-method override
)

Example: Multi-Tenant App

from raysurfer import RaysurferClient
from claude_agent_sdk import ClaudeAgentOptions

async def handle_customer_request(customer_id: str, task: str):
    options = ClaudeAgentOptions(allowed_tools=["Read", "Write", "Bash"])

    # Pass the customer ID as workspace_id
    async with RaysurferClient(options, workspace_id=customer_id) as client:
        await client.query(task)
        async for msg in client.response():
            yield msg

FAQ

Can snippets leak between workspaces?

No. Workspaces are completely isolated. There is no code path that allows cross-workspace access.

What if I don’t set a workspace_id?

Your organization’s shared cache is used. All users in your org see the same snippets — fine for internal use, but not for multi-tenant scenarios.

What does a workspace actually control?

A workspace is just a cache partition — it siloes which cached code snippets are visible, nothing more. Raysurfer does not manage your underlying permissions (database access, column-level security, API scopes, etc.). Your application decides which workspace_id to assign each user based on whatever permission model you already have. For example, if user A can query columns M and N, and user B can query columns M, N, O, and P, give them different workspace IDs (e.g. "mn" and "mnop"). User A’s cached scripts will only ever reference their allowed columns, and user B’s are completely separate. Raysurfer guarantees no cross-workspace leakage — your app still enforces the actual data permissions.

What should I use as the workspace_id?

Any string that uniquely maps to a permission boundary — a customer ID, a role name, a hash of allowed scopes, or a permission profile key. It’s an arbitrary string you choose.

How does workspace search work?

When you provide a workspace_id, Raysurfer searches both the workspace namespace and the org-wide namespace in parallel, merges results by score, and returns the top results. This means workspace-specific snippets compete on equal footing with company-wide snippets.

Can I query multiple workspaces at once?

No. Each client is scoped to one workspace. Query separately if needed.