code<spar>

Multi-Tenant / Organizations

How to set up multi-tenant CodeSpar deployments with organizations, project isolation, x-org-id header scoping, file storage structure, Clerk Organizations integration, and API examples.

Multi-Tenant / Organizations

CodeSpar supports multi-tenant deployments where multiple organizations share a single instance. Each organization has isolated projects, agents, audit trails, and storage.

Overview

CodeSpar Instance
├── Org: Acme Corp (org-acme-corp)
│   ├── Project: API Service (agent-001)
│   ├── Project: Frontend (agent-002)
│   └── Project: Mobile App (agent-003)

├── Org: Globex Inc (org-globex)
│   ├── Project: Platform (agent-004)
│   └── Project: Dashboard (agent-005)

└── Org: Initech (org-initech)
    └── Project: Monolith (agent-006)

Each organization is fully isolated — agents, projects, audit logs, and user identities are scoped per organization.


x-org-id Header Scoping

All API requests can be scoped to a specific organization using the x-org-id header:

# List projects for Acme Corp
curl http://localhost:3000/api/projects \
  -H "x-org-id: org-acme-corp"
 
# List projects for Globex Inc
curl http://localhost:3000/api/projects \
  -H "x-org-id: org-globex"

Without the header, requests operate in the default (single-tenant) context.

How x-org-id Works

When the x-org-id header is present on a request:

  1. The API validates that the organization exists
  2. All data reads are filtered to that organization's scope
  3. All data writes are stored in that organization's directory or database partition
  4. RBAC is evaluated against the user's role in that specific organization

When the header is absent, the API operates in single-tenant mode — all data is accessed without org filtering.

Scoped Endpoints

All major endpoints respect x-org-id:

EndpointWith x-org-idWithout x-org-id
GET /api/agentsReturns agents for the orgReturns all agents
GET /api/projectsReturns projects for the orgReturns all projects
GET /api/auditReturns audit entries for the orgReturns all audit entries
POST /api/projectsCreates project in the orgCreates project in default scope
GET /api/memoryReturns memory stats for the orgReturns global memory stats
DELETE /api/projects/:idDeletes only if project belongs to orgDeletes without org check

Creating Organizations

Via API

curl -X POST http://localhost:3000/api/orgs \
  -H "Content-Type: application/json" \
  -d '{
    "name": "Acme Corp",
    "slug": "acme-corp"
  }'

Response:

{
  "id": "org-acme-corp",
  "name": "Acme Corp",
  "slug": "acme-corp",
  "projectCount": 0,
  "memberCount": 0,
  "createdAt": "2024-01-10T10:00:00Z",
  "storageDir": ".codespar/orgs/org-acme-corp/"
}

The organization ID is automatically generated as org-{slug}.

After Creating an Organization

Once created, you can immediately start scoping operations to it:

# Create a project in the new org
curl -X POST http://localhost:3000/api/projects \
  -H "Content-Type: application/json" \
  -H "x-org-id: org-acme-corp" \
  -d '{
    "repo": "acme/api-service",
    "name": "API Service"
  }'
 
# List projects in the org
curl http://localhost:3000/api/projects \
  -H "x-org-id: org-acme-corp"
 
# Check audit trail for the org
curl http://localhost:3000/api/audit \
  -H "x-org-id: org-acme-corp"

List Organizations

curl http://localhost:3000/api/orgs

Get Organization Details

curl http://localhost:3000/api/orgs/org-acme-corp

Returns the organization with all its projects and members. See Organization API for full details.


File Storage — Organization Directory Structure

When using file-based storage (DATABASE_URL not set), each organization gets an isolated directory under .codespar/orgs/:

.codespar/
  config.json                    # Global instance configuration
  orgs/
    org-acme-corp/
      memory.json                # Organization-level memory/context
      audit.json                 # Organization-level audit trail
      config.json                # Organization configuration
      members.json               # Member list and roles
      projects/
        proj-001/
          config.json            # Project configuration
          context/               # Vector store embeddings
          audit/                 # Project-level audit entries
        proj-002/
          config.json
          context/
          audit/
    org-globex/
      memory.json
      audit.json
      config.json
      members.json
      projects/
        proj-004/
          config.json
          context/
          audit/

Key Files

FileLocationContents
memory.json.codespar/orgs/<orgId>/memory.jsonOrganization-level context and memory, including shared knowledge across projects
audit.json.codespar/orgs/<orgId>/audit.jsonOrganization-level audit trail with hash chain integrity
config.json.codespar/orgs/<orgId>/config.jsonOrganization settings: name, slug, autonomy defaults, notification preferences
members.json.codespar/orgs/<orgId>/members.jsonMember list with identity IDs, display names, roles, and join dates

Isolation Guarantees

DataIsolation Level
Project configurationPer-org directory
Agent statePer-project within org
Audit trailPer-org, separate log files
Vector storePer-project within org
User identitiesPer-org (same user can be in multiple orgs)
RBAC rolesPer-org (different roles per org)
Memory/contextPer-org (organization-level), per-project (project-level)

A user can be an owner in one organization and a reviewer in another.

Docker Volume Mount

In containerized deployments, mount the entire .codespar/ directory as a volume to persist all organization data:

volumes:
  - codespar-data:/app/.codespar

Or use CODESPAR_WORK_DIR to set a custom location:

environment:
  - CODESPAR_WORK_DIR=/data/codespar
volumes:
  - codespar-data:/data/codespar

How Project Isolation Works Per Organization

Projects within an organization are fully isolated from each other:

ResourceIsolation
Git repositoryEach project links to a different repo
Agent instanceEach project has its own Project Agent
Autonomy levelSet independently per project
Audit trailLogged per project (queryable per org)
Vector storeSeparate embeddings per project
WebhooksIndependent webhook per repo

Projects in different organizations cannot see or interact with each other. The Coordinator Agent can orchestrate across projects within the same organization (e.g., cascading deploys), but never across organizations.

Cross-Org Isolation Example

# User in org-acme-corp lists projects — only sees Acme's projects
curl http://localhost:3000/api/projects -H "x-org-id: org-acme-corp"
# Returns: API Service, Frontend, Mobile App

# Same user in org-globex lists projects — only sees Globex's projects
curl http://localhost:3000/api/projects -H "x-org-id: org-globex"
# Returns: Platform, Dashboard

# Attempting to delete a Globex project from Acme's scope fails
curl -X DELETE http://localhost:3000/api/projects/proj-004 \
  -H "x-org-id: org-acme-corp"
# Returns: 404 Project not found

Adding Projects to an Organization

Create a project scoped to an organization:

curl -X POST http://localhost:3000/api/projects \
  -H "Content-Type: application/json" \
  -H "x-org-id: org-acme-corp" \
  -d '{
    "name": "API Service",
    "repo": "acme/api-service"
  }'

The project, its agent, and all associated data will be stored under the organization's directory.

Via Chat Command

When a user sends commands from a channel, the organization is determined by the channel configuration. A Slack workspace or Discord guild can be mapped to a specific organization.

@codespar link acme/api-service

The project is created in the organization associated with the current channel.


Managing Members

Members are managed per organization. Each member has a role that determines their permissions within that organization.

Default Roles

When a user first interacts with an agent in an organization, they are assigned the read-only role by default. An owner or maintainer can upgrade their role.

Role Assignment

Roles are currently managed via the organization configuration file or the dashboard (future). The API for role management is:

# View org members
curl http://localhost:3000/api/orgs/org-acme-corp

The response includes a members array with role information:

{
  "members": [
    {
      "identityId": "identity-001",
      "displayName": "John Silva",
      "role": "owner",
      "joinedAt": "2024-01-10T10:00:00Z"
    },
    {
      "identityId": "identity-002",
      "displayName": "Alice Chen",
      "role": "maintainer",
      "joinedAt": "2024-01-11T09:00:00Z"
    }
  ]
}

Clerk Organizations Integration (Dashboard)

The CodeSpar dashboard (future) integrates with Clerk for organization management.

How Clerk Organizations Map to CodeSpar

Clerk Organizations provide a managed authentication and authorization layer. Each Clerk Organization maps 1:1 to a CodeSpar organization:

Clerk ConceptCodeSpar Concept
OrganizationOrganization (x-org-id)
Organization memberIdentity with RBAC role
Organization roleRBAC role
Organization invitationMember onboarding

Clerk-to-CodeSpar Role Mapping

Clerk RoleCodeSpar Role
org:adminowner
org:membermaintainer
org:viewerread-only

Custom Clerk roles can be mapped to any CodeSpar role via configuration.

Dashboard: OrganizationSwitcher Component

The dashboard sidebar includes a Clerk OrganizationSwitcher component that allows users to switch between organizations. When the user switches:

  1. The active Clerk organization changes
  2. The dashboard updates the x-org-id header on all subsequent API requests
  3. All displayed data (projects, agents, audit logs) updates to reflect the selected organization
  4. RBAC permissions are re-evaluated against the user's role in the new organization
┌─────────────────────────────────┐
│  Dashboard Sidebar              │
│  ┌───────────────────────────┐  │
│  │ 🏢 Acme Corp         ▼  │  │  ← OrganizationSwitcher
│  │   ○ Globex Inc           │  │
│  │   ○ Initech              │  │
│  └───────────────────────────┘  │
│                                 │
│  Projects                       │
│  • API Service (active)         │
│  • Frontend (idle)              │
│  • Mobile App (active)          │
│                                 │
│  Settings                       │
│  Audit Log                      │
│  Members                        │
└─────────────────────────────────┘

API: x-org-id Header with Clerk

When the dashboard makes API requests, it includes the active Clerk organization ID as the x-org-id header:

// Dashboard API client
const response = await fetch("/api/projects", {
  headers: {
    "x-org-id": activeOrganization.id,  // e.g., "org-acme-corp"
    "Authorization": `Bearer ${sessionToken}`,
  },
});

All API endpoints respect this header and scope their data accordingly.

Setup

When using the dashboard with Clerk:

# Clerk environment variables (dashboard only)
NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY=pk_...
CLERK_SECRET_KEY=sk_...
CLERK_WEBHOOK_SECRET=whsec_...

Clerk webhooks notify CodeSpar when:

  • A new organization is created → CodeSpar creates a matching org directory
  • A member is added or removed → CodeSpar updates members.json
  • A member's role changes → CodeSpar updates the member's RBAC role

Example: Full Multi-Tenant Workflow

Here is a complete example of setting up a multi-tenant deployment from scratch.

1. Create an Organization

curl -X POST http://localhost:3000/api/orgs \
  -H "Content-Type: application/json" \
  -d '{"name": "Acme Corp", "slug": "acme-corp"}'

2. Create Projects in the Organization

# API Service
curl -X POST http://localhost:3000/api/projects \
  -H "Content-Type: application/json" \
  -H "x-org-id: org-acme-corp" \
  -d '{"repo": "acme/api-service", "name": "API Service"}'
 
# Frontend
curl -X POST http://localhost:3000/api/projects \
  -H "Content-Type: application/json" \
  -H "x-org-id: org-acme-corp" \
  -d '{"repo": "acme/frontend", "name": "Frontend"}'

3. Verify the Organization Structure

# List all projects in the org
curl http://localhost:3000/api/projects \
  -H "x-org-id: org-acme-corp"
 
# Check the file storage structure
ls -la .codespar/orgs/org-acme-corp/
# config.json  members.json  memory.json  audit.json  projects/
 
ls -la .codespar/orgs/org-acme-corp/projects/
# proj-001/  proj-002/

4. Operations Are Scoped

From this point, all operations using -H "x-org-id: org-acme-corp" are scoped:

# Audit log shows only Acme's events
curl http://localhost:3000/api/audit -H "x-org-id: org-acme-corp"
 
# Agents list shows only Acme's agents
curl http://localhost:3000/api/agents -H "x-org-id: org-acme-corp"
 
# Memory stats are org-scoped
curl http://localhost:3000/api/memory -H "x-org-id: org-acme-corp"

Single-Tenant to Multi-Tenant Migration

If you started with a single-tenant setup and want to migrate to multi-tenant:

  1. Create an organization via the API
  2. Move existing projects into the organization
  3. Update channel configurations to map to the organization
  4. Assign roles to existing users

The existing .codespar/ data can be migrated by moving project directories into the organization structure:

# Before (single-tenant)
.codespar/
  projects/
    proj-001/
    proj-002/
 
# After (multi-tenant)
.codespar/
  orgs/
    org-acme-corp/
      memory.json
      audit.json
      config.json
      members.json
      projects/
        proj-001/
        proj-002/

Best Practices

PracticeRecommendation
Organization namingUse clear, unique slugs (e.g., acme-corp, not org1)
Role assignmentStart with read-only, promote as needed
Project namingMatch repository names for clarity
Autonomy levelsSet per-project based on project criticality
Audit reviewRegularly review org-level audit logs
Member offboardingRemove members promptly when they leave the team
StorageAlways use Docker volumes to persist .codespar/orgs/ data

Next Steps