Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
35 commits
Select commit Hold shift + click to select a range
9de614d
feat: Make disk file saving more robust
paultranvan Nov 19, 2025
9c93fe3
tests: Add unit test for disk file saving
paultranvan Nov 19, 2025
bb966fc
refactor: Move file serialization in common module
paultranvan Nov 19, 2025
7adf225
feat: Add tools endpoint
paultranvan Nov 19, 2025
b17bf1d
new release 1.1.7
Ahmath-Gadji Jan 22, 2026
04ef5ed
feat(workspace)!: add workspace models with CRUD methods
paultranvan Mar 6, 2026
bcdfb1e
feat(workspace): add alembic migration for workspaces tables
paultranvan Mar 6, 2026
76c8ac4
feat(workspace): expose workspace methods on MilvusDB Ray actor
paultranvan Mar 6, 2026
6d3b665
feat(workspace): add workspace CRUD and file management router
paultranvan Mar 6, 2026
1ab2d08
feat(workspace): add workspace filtering to search and chat pipelines
paultranvan Mar 6, 2026
1e2620d
feat(workspace): add workspace_ids parameter to file upload
paultranvan Mar 6, 2026
42c06cd
test(workspace): add API integration tests
paultranvan Mar 6, 2026
a95bd04
docs(workspace): add workspace documentation and update data model
paultranvan Mar 6, 2026
21d5466
chore: Exlude superpowers plans
paultranvan Mar 6, 2026
7b089e2
docs: add alternative migration methods to sql_migration guide
paultranvan Mar 6, 2026
b40f37d
fix(workspace): return 201 on creation, use named user dependency
EnjoyBacon7 Mar 11, 2026
81f0ee4
fix(workspace): align list_workspaces response shape with get_workspace
EnjoyBacon7 Mar 11, 2026
38207de
fix(workspace): return 404 when removing a file not in the workspace
EnjoyBacon7 Mar 11, 2026
70d5da5
fix(workspace): prevent partial deletion from crashing delete_workspace
EnjoyBacon7 Mar 11, 2026
9731c96
fix(docs): correct workspace endpoint base path from /indexer to /par…
EnjoyBacon7 Mar 11, 2026
db041be
fix(docs): correct workspace_files.file_id description — no FK constr…
EnjoyBacon7 Mar 11, 2026
45c0dc5
fix(workspace): make workspace actor methods async def for consistenc…
EnjoyBacon7 Mar 11, 2026
ea78003
fix(security): validate workspace belongs to target partition before …
EnjoyBacon7 Mar 11, 2026
5100e19
fix(workspace): return 409 on duplicate workspace_id instead of unhan…
EnjoyBacon7 Mar 11, 2026
131e143
fix(workspace): make add_files_to_workspace idempotent with ON CONFLI…
EnjoyBacon7 Mar 11, 2026
030bae0
fix(workspace): scope remove_file_from_all_workspaces to the file's p…
EnjoyBacon7 Mar 11, 2026
a8ed1ad
fix: defer workspace association until after file indexing completes
EnjoyBacon7 Mar 12, 2026
f531dcc
fix(workspace): route all workspace Ray actor calls through call_ray_…
EnjoyBacon7 Mar 12, 2026
060a910
fix(workspace): allow vendor-specific extra fields on request models
EnjoyBacon7 Mar 12, 2026
fe36e19
fix(workspace): pin workspace search to workspace's partition in asyn…
EnjoyBacon7 Mar 12, 2026
24d05fb
fix: guard workspace association in add_file against DB errors
EnjoyBacon7 Mar 12, 2026
a9f8c05
fix: preserve workspace filter when partition==["all"]
EnjoyBacon7 Mar 12, 2026
66bbf7a
fix: report accurate orphaned file counts on workspace delete
EnjoyBacon7 Mar 12, 2026
1f18e54
fix(lint): fix ruff errors in files.py, test_files.py, indexer.py, se…
EnjoyBacon7 Mar 12, 2026
66232a2
fix(lint): fix ruff formatting in test_files.py
EnjoyBacon7 Mar 12, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,7 @@ node_modules/
# AI
.claude
.planning
docs/plans
docs/plans/

# Build artifacts
*.egg-info/
1 change: 1 addition & 0 deletions .hydra_config/config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -177,6 +177,7 @@ ray:
indexer:
max_task_retries: ${oc.decode:${oc.env:RAY_MAX_TASK_RETRIES, 2}}
serialize_timeout: ${oc.decode:${oc.env:INDEXER_SERIALIZE_TIMEOUT, 3600}}
vectordb_timeout: ${oc.decode:${oc.env:VECTORDB_TIMEOUT, 30}}
concurrency_groups:
default: ${oc.decode:${oc.env:INDEXER_DEFAULT_CONCURRENCY, 1000}}
update: ${oc.decode:${oc.env:INDEXER_UPDATE_CONCURRENCY, 100}}
Expand Down
3 changes: 3 additions & 0 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -118,6 +118,7 @@ The `extra` field in API responses is a JSON string: `{"sources": [filtered_sour
- `partition.py` - Partition management (multi-tenant document collections)
- `users.py` - User and membership management
- `queue.py` - Task queue monitoring
- `workspaces.py` - Workspace CRUD and file management
- `tools.py` - Tools like `extractText` at `/v1/tools/execute` (tool param requires JSON: `{"name": "extractText"}`)

### User Management & Authentication
Expand All @@ -129,6 +130,8 @@ The system uses token-based authentication with role-based access control (RBAC)
- `files` - File records with `file_id`, `partition_name`, `file_metadata`, `created_by` (FK to users), `relationship_id`, `parent_id`
- `partition_memberships` - Join table linking users to partitions with roles (`owner`, `editor`, `viewer`)
- `partitions` - Document collections with cascade delete to files and memberships
- `workspaces` - Named file subsets within a partition for scoped search/chat
- `workspace_files` - Join table linking workspaces to files

**Authentication Flow** (`openrag/api.py` - `AuthMiddleware`):
1. Token extracted from `Authorization: Bearer <token>` header (or `?token=` query param for `/static` routes)
Expand Down
1 change: 1 addition & 0 deletions docs/content/docs/documentation/API.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -496,6 +496,7 @@ curl -X 'POST' 'http://localhost:8080/v1/chat/completions' \
}'
```


### 🔧 Tools

Tools are useful features that can be called directly by the client.
Expand Down
51 changes: 51 additions & 0 deletions docs/content/docs/documentation/data_model.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,10 @@ Implemented using **SQLAlchemy ORM** with PostgreSQL as the backend.
erDiagram
partitions ||--o{ files : contains
partitions ||--o{ partition_memberships : has
partitions ||--o{ workspaces : has
users ||--o{ partition_memberships : belongs_to
workspaces ||--o{ workspace_files : has
files ||--o{ workspace_files : referenced_by

partitions {
int id PK
Expand Down Expand Up @@ -50,6 +53,21 @@ erDiagram
varchar role
datetime added_at
}

workspaces {
int id PK
varchar workspace_id UK
varchar partition_name FK
varchar display_name
int created_by FK
datetime created_at
}

workspace_files {
int id PK
varchar workspace_id FK
varchar file_id FK
}
```

### `users`
Expand Down Expand Up @@ -133,6 +151,38 @@ Defines the many-to-many relationship between users and partitions with role-bas

---

### `workspaces`

Groups files within a partition into named subsets for scoped search and chat. See [Workspaces](/openrag/documentation/workspaces/) for full details.

| Column | Type | Description |
|------------------|------|-------------|
| `id` | Integer (PK) | Internal identifier |
| `workspace_id` | String (unique) | Client-facing workspace identifier |
| `partition_name` | String (FK → `partitions.partition`, CASCADE) | Owning partition |
| `display_name` | String (nullable) | Human-readable name |
| `created_by` | Integer (FK → `users.id`, SET NULL) | User who created the workspace |
| `created_at` | DateTime | Timestamp of creation |

**Relationships:** `files` many-to-many → `File` (via `workspace_files`)

---

### `workspace_files`

Join table linking workspaces to files.

| Column | Type | Description |
|------------------|------|-------------|
| `id` | Integer (PK) | Internal identifier |
| `workspace_id` | String (FK → `workspaces.workspace_id`, CASCADE) | Workspace reference |
| `file_id` | String | File reference (no FK constraint — referential integrity enforced at application level) |

**Constraints:**
- `UniqueConstraint(workspace_id, file_id)` → a file appears at most once per workspace

---

## Milvus Schema

Milvus stores document chunks with their vector embeddings. The collection uses dynamic fields for flexible metadata.
Expand Down Expand Up @@ -193,6 +243,7 @@ flowchart LR
|------|:----------:|:------:|-----------|
| Partition metadata | ✓ | - | Referential integrity, access control |
| File inventory | ✓ | - | Single source of truth for uploaded files |
| Workspace membership | ✓ | - | File grouping resolved at query time |
| User accounts & roles | ✓ | - | Authentication, ACID compliance |
| Document chunks | - | ✓ | Optimized for vector operations |
| Dense embeddings | - | ✓ | HNSW similarity search |
Expand Down
26 changes: 23 additions & 3 deletions docs/content/docs/documentation/sql_migration.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -42,17 +42,37 @@ It will create a new migration script in the `openrag/scripts/migrations/alembic

### Step 2: Apply the Migration

Once you've reviewed the generated migration script and confirmed it looks correct, apply it to your database:
Once you've reviewed the generated migration script and confirmed it looks correct, apply it to your database.

```bash title="Apply migrations"
#### Option A: Docker (production)

Rebuilds the image to ensure the migration runs against the exact code being deployed:

```bash title="Apply migrations (Docker)"
docker compose up -d rdb
docker compose \
run --no-deps --build --rm \
--entrypoint "uv run alembic -c /app/openrag/scripts/migrations/alembic/alembic.ini upgrade head" \
openrag; docker compose down
```

This upgrades your database schema to the latest revision.
#### Option B: Docker exec (stack already running)

If the stack is already running, skip the rebuild:

```bash title="Apply migrations (exec)"
docker compose exec openrag \
uv run alembic -c /app/openrag/scripts/migrations/alembic/alembic.ini upgrade head
```

#### Option C: Local (development)

Run alembic directly with your local venv — no Docker needed:

```bash title="Apply migrations (local)"
DATABASE_URL=postgresql://user:pass@localhost:5432/openrag \
uv run alembic -c openrag/scripts/migrations/alembic/alembic.ini upgrade head
```

:::tip[Automatic Migrations on Code Update]
When you pull the latest changes to your local code, check if a new migration script has been added. If so, make sure to apply the migration before restarting the service.
Expand Down
213 changes: 213 additions & 0 deletions docs/content/docs/documentation/workspaces.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,213 @@
---
title: Workspaces
description: Group files into workspaces for scoped search and chat
---

Workspaces let you organize files within a partition into named subsets. When searching or chatting, you can target a specific workspace so that only its files are considered — without affecting the underlying partition structure.

---

## Concepts

- A **workspace** belongs to exactly one partition
- A file can belong to **multiple workspaces** (or none)
Comment thread
EnjoyBacon7 marked this conversation as resolved.
- Workspaces do not duplicate files — they reference existing partition files
- Deleting a workspace deletes **orphaned files** (files not in any other workspace) from the partition automatically
Comment thread
EnjoyBacon7 marked this conversation as resolved.
- Deleting a file removes it from all workspaces it belongs to

---

## Data Model

```mermaid
erDiagram
partitions ||--o{ workspaces : contains
workspaces ||--o{ workspace_files : has
files ||--o{ workspace_files : referenced_by

workspaces {
int id PK
varchar workspace_id UK
varchar partition_name FK
varchar display_name
int created_by FK
datetime created_at
}

workspace_files {
int id PK
varchar workspace_id FK
varchar file_id FK
}
```

**Constraints:**
- `UniqueConstraint(workspace_id)` — workspace IDs are globally unique
- `UniqueConstraint(workspace_id, file_id)` on `workspace_files` — a file appears at most once per workspace
- Cascade delete: dropping a workspace removes its `workspace_files` rows

---

## API Endpoints

All workspace endpoints live under `/partition/{partition}/workspaces`.

### Workspace CRUD

| Method | Endpoint | Auth | Description |
|--------|----------|------|-------------|
| POST | `/partition/{partition}/workspaces` | Editor | Create a workspace |
| GET | `/partition/{partition}/workspaces` | Viewer | List all workspaces in partition |
| GET | `/partition/{partition}/workspaces/{workspace_id}` | Viewer | Get workspace details |
| DELETE | `/partition/{partition}/workspaces/{workspace_id}` | Owner | Delete workspace and orphaned files |

### Workspace File Management

| Method | Endpoint | Auth | Description |
|--------|----------|------|-------------|
| POST | `/partition/{partition}/workspaces/{workspace_id}/files` | Editor | Add files to workspace |
| GET | `/partition/{partition}/workspaces/{workspace_id}/files` | Viewer | List files in workspace |
| DELETE | `/partition/{partition}/workspaces/{workspace_id}/files/{file_id}` | Editor | Remove file from workspace |

---

### Create a Workspace

```bash
curl -X POST "$BASE_URL/partition/my-partition/workspaces" \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{"workspace_id": "project-alpha", "display_name": "Project Alpha"}'
```

```json
{"status": "created", "workspace_id": "project-alpha"}
```

### List Workspaces

```bash
curl "$BASE_URL/partition/my-partition/workspaces" \
-H "Authorization: Bearer $TOKEN"
```

```json
{
"workspaces": [
{
"workspace_id": "project-alpha",
"partition_name": "my-partition",
"display_name": "Project Alpha",
"created_by": 1,
"created_at": "2026-03-06T10:00:00"
}
]
}
```

### Add Files to a Workspace

```bash
curl -X POST "$BASE_URL/partition/my-partition/workspaces/project-alpha/files" \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{"file_ids": ["report.pdf", "notes.md"]}'
```

```json
{"status": "added", "file_ids": ["report.pdf", "notes.md"]}
```

### Delete a Workspace

```bash
curl -X DELETE "$BASE_URL/partition/my-partition/workspaces/project-alpha" \
-H "Authorization: Bearer $TOKEN"
```

```json
{"status": "deleted", "orphaned_files_deleted": 1}
```

Files that belonged **only** to the deleted workspace are automatically removed from the partition. Files shared with other workspaces are preserved.

---

## Upload with Workspace Assignment

Files can be added to one or more workspaces at upload time using the `workspace_ids` form parameter:

```bash
curl -X POST "$BASE_URL/indexer/partition/my-partition/file/my-file-id" \
-H "Authorization: Bearer $TOKEN" \
-F "file=@document.pdf" \
-F 'metadata={"mimetype": "application/pdf"}' \
-F 'workspace_ids=["project-alpha", "project-beta"]'
```

The `workspace_ids` field accepts a JSON array of workspace IDs. Each workspace must exist in the target partition, otherwise the request is rejected with a 404.

---

## Workspace-Scoped Search

Pass the `workspace` query parameter to restrict search results to files in that workspace:

```bash
curl "$BASE_URL/search/partition/my-partition?text=quarterly+results&workspace=project-alpha" \
-H "Authorization: Bearer $TOKEN"
```

Only chunks from files belonging to the `project-alpha` workspace are returned.

### Multi-Partition Search

The `workspace` parameter also works with multi-partition search:

```bash
curl "$BASE_URL/search?partitions=my-partition&text=quarterly+results&workspace=project-alpha" \
-H "Authorization: Bearer $TOKEN"
```

---

## Workspace-Scoped Chat

To scope a chat completion to a workspace, include the `workspace` field in the request metadata:

```bash
curl -X POST "$BASE_URL/v1/chat/completions" \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{
"model": "openrag-my-partition",
"messages": [{"role": "user", "content": "Summarize the Q1 results"}],
"metadata": {"workspace": "project-alpha"}
}'
```

The RAG pipeline resolves the workspace to its file list and filters the vector search accordingly.

---

## Deletion Behavior

### Deleting a Workspace

```mermaid
flowchart LR
A[Delete workspace] --> B[Find workspace files]
B --> C{File in other workspaces?}
C -->|Yes| D[Keep file]
C -->|No| E[Delete orphaned file from partition]
D --> F[Done]
E --> F
```

### Deleting a File

When a file is deleted from a partition (via `DELETE /partition/{partition}/file/{file_id}`), it is automatically removed from all workspaces that reference it.

### Deleting a Partition

When a partition is deleted, all its workspaces and workspace-file associations are cascade-deleted along with the files.
Loading
Loading