@rool-dev/sdk 0.2.0-dev.64c2b97 → 0.2.0-dev.6769bdc

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -1,12 +1,14 @@
1
1
  # Rool SDK
2
2
 
3
- The TypeScript SDK for Rool Spaces, a persistent and collaborative environment for organizing objects.
3
+ The TypeScript SDK for Rool, a persistent and collaborative environment for organizing objects.
4
4
 
5
- Rool Spaces enables you to build applications where AI operates on a structured world model rather than a text conversation. The context for all AI operations is the full object graph, allowing the system to reason about, update, and expand the state of your application directly.
5
+ Rool enables you to build applications where AI operates on a structured world model rather than a text conversation. The context for all AI operations is the full object graph, allowing the system to reason about, update, and expand the state of your application directly.
6
6
 
7
7
  Use Rool to programmatically instruct agents to generate content, research topics, or reorganize data. The client manages authentication, real-time synchronization, and media storage, supporting both single-user and multi-user workflows.
8
8
 
9
9
  **Core primitives:**
10
+ - **Spaces** — Containers for objects, schema, metadata, and channels
11
+ - **Channels** — A space + conversationId pair. All object and AI operations go through a channel.
10
12
  - **Objects** — Key-value records with any fields you define. References between objects are data fields whose values are object IDs.
11
13
  - **AI operations** — Create, update, or query objects using natural language and `{{placeholders}}`
12
14
 
@@ -30,48 +32,79 @@ if (!authenticated) {
30
32
  client.login('My App'); // Redirects to auth page, shows "Sign in to My App"
31
33
  }
32
34
 
33
- // Create a new space (or open existing with client.openSpace('id'))
35
+ // Create a new space, then open a channel on it
34
36
  const space = await client.createSpace('Solar System');
37
+ const channel = await space.openChannel('main');
38
+
39
+ // Define the schema — what types of objects exist and their fields
40
+ await channel.createCollection('body', [
41
+ { name: 'name', type: { kind: 'string' } },
42
+ { name: 'mass', type: { kind: 'string' } },
43
+ { name: 'radius', type: { kind: 'string' } },
44
+ { name: 'orbits', type: { kind: 'maybe', inner: { kind: 'ref' } } },
45
+ ]);
35
46
 
36
47
  // Create objects with AI-generated content using {{placeholders}}
37
- const { object: sun } = await space.createObject({
48
+ const { object: sun } = await channel.createObject({
38
49
  data: {
39
- type: 'star',
40
50
  name: 'Sun',
41
51
  mass: '{{mass in solar masses}}',
42
- temperature: '{{surface temperature in Kelvin}}'
52
+ radius: '{{radius in km}}'
43
53
  }
44
54
  });
45
55
 
46
- const { object: earth } = await space.createObject({
56
+ const { object: earth } = await channel.createObject({
47
57
  data: {
48
- type: 'planet',
49
58
  name: 'Earth',
50
59
  mass: '{{mass in Earth masses}}',
51
60
  radius: '{{radius in km}}',
52
- orbitalPeriod: '{{orbital period in days}}',
53
61
  orbits: sun.id // Reference to the sun object
54
62
  }
55
63
  });
56
64
 
57
65
  // Use the AI agent to work with your data
58
- const { message, objects } = await space.prompt(
66
+ const { message, objects } = await channel.prompt(
59
67
  'Add the other planets in our solar system, each referencing the Sun'
60
68
  );
61
69
  console.log(message); // AI explains what it did
62
70
  console.log(`Created ${objects.length} objects`);
63
71
 
64
72
  // Query with natural language
65
- const { objects: innerPlanets } = await space.findObjects({
73
+ const { objects: innerPlanets } = await channel.findObjects({
66
74
  prompt: 'planets closer to the sun than Earth'
67
75
  });
68
76
 
69
77
  // Clean up
70
- space.close();
78
+ channel.close();
71
79
  ```
72
80
 
73
81
  ## Core Concepts
74
82
 
83
+ ### Spaces and Channels
84
+
85
+ A **space** is a container that holds objects, schema, metadata, and channels. A **channel** is a space + conversationId pair — it's the handle you use for all object and AI operations.
86
+
87
+ There are two main handles:
88
+ - **`RoolSpace`** — Lightweight admin handle for user management, link access, channel management, and export. No real-time subscription.
89
+ - **`RoolChannel`** — Full real-time handle for objects, AI prompts, media, schema, and undo/redo.
90
+
91
+ ```typescript
92
+ // Open a space for admin operations
93
+ const space = await client.openSpace('space-id');
94
+ await space.addUser(userId, 'editor');
95
+ await space.setLinkAccess('viewer');
96
+
97
+ // Open a channel for object and AI operations
98
+ const channel = await client.openChannel('space-id', 'my-conversation');
99
+ await channel.prompt('Create some planets');
100
+
101
+ // Or open a channel via the space handle
102
+ const channel2 = await space.openChannel('research');
103
+ await channel2.prompt('Analyze the data'); // Independent conversation
104
+ ```
105
+
106
+ The `conversationId` is fixed when you open a channel and cannot be changed. To use a different conversation, open a new channel. Both channels share the same objects and schema — only the conversation history differs.
107
+
75
108
  ### Objects & References
76
109
 
77
110
  **Objects** are plain key-value records. The `id` field is reserved; everything else is application-defined.
@@ -98,7 +131,7 @@ Use `{{description}}` in field values to have AI generate content:
98
131
 
99
132
  ```typescript
100
133
  // Create with AI-generated content
101
- await space.createObject({
134
+ await channel.createObject({
102
135
  data: {
103
136
  type: 'article',
104
137
  headline: '{{catchy headline about coffee}}',
@@ -107,12 +140,12 @@ await space.createObject({
107
140
  });
108
141
 
109
142
  // Update existing content with AI
110
- await space.updateObject('abc123', {
143
+ await channel.updateObject('abc123', {
111
144
  prompt: 'Make the body shorter and more casual'
112
145
  });
113
146
 
114
147
  // Add new AI-generated field to existing object
115
- await space.updateObject('abc123', {
148
+ await channel.updateObject('abc123', {
116
149
  data: { summary: '{{one-sentence summary}}' }
117
150
  });
118
151
  ```
@@ -127,17 +160,17 @@ Undo/redo works on **checkpoints**, not individual operations. Call `checkpoint(
127
160
 
128
161
  ```typescript
129
162
  // Create a checkpoint before user action
130
- await space.checkpoint('Delete object');
131
- await space.deleteObjects([objectId]);
163
+ await channel.checkpoint('Delete object');
164
+ await channel.deleteObjects([objectId]);
132
165
 
133
166
  // User can now undo back to the checkpoint
134
- if (await space.canUndo()) {
135
- await space.undo(); // Restores the deleted object
167
+ if (await channel.canUndo()) {
168
+ await channel.undo(); // Restores the deleted object
136
169
  }
137
170
 
138
171
  // Redo reapplies the undone action
139
- if (await space.canRedo()) {
140
- await space.redo(); // Deletes the object again
172
+ if (await channel.canRedo()) {
173
+ await channel.redo(); // Deletes the object again
141
174
  }
142
175
  ```
143
176
 
@@ -150,7 +183,7 @@ In collaborative scenarios, conflicting changes (modified by others since your c
150
183
  Fields starting with `_` (e.g., `_ui`, `_cache`) are hidden from AI but otherwise behave like normal fields — they sync in real-time, persist to the server, support undo/redo, and are visible to all users of the Space. Use them for UI state, positions, or other data the AI shouldn't see or modify:
151
184
 
152
185
  ```typescript
153
- await space.createObject({
186
+ await channel.createObject({
154
187
  data: {
155
188
  title: 'My Article',
156
189
  author: "John Doe",
@@ -170,7 +203,7 @@ Events fire for both local and remote changes. The `source` field indicates orig
170
203
 
171
204
  ```typescript
172
205
  // All UI updates happen in one place, regardless of change source
173
- space.on('objectUpdated', ({ objectId, object, source }) => {
206
+ channel.on('objectUpdated', ({ objectId, object, source }) => {
174
207
  renderObject(objectId, object);
175
208
  if (source === 'remote_agent') {
176
209
  doLayout(); // AI might have added content
@@ -178,7 +211,7 @@ space.on('objectUpdated', ({ objectId, object, source }) => {
178
211
  });
179
212
 
180
213
  // Caller just makes the change - event handler does the UI work
181
- space.updateObject(objectId, { prompt: 'expand this' });
214
+ channel.updateObject(objectId, { prompt: 'expand this' });
182
215
  ```
183
216
 
184
217
  ### Custom Object IDs
@@ -186,7 +219,7 @@ space.updateObject(objectId, { prompt: 'expand this' });
186
219
  By default, `createObject` generates a 6-character alphanumeric ID. Provide your own via `data.id`:
187
220
 
188
221
  ```typescript
189
- await space.createObject({ data: { id: 'article-42', title: 'The Meaning of Life' } });
222
+ await channel.createObject({ data: { id: 'article-42', title: 'The Meaning of Life' } });
190
223
  ```
191
224
 
192
225
  **Why use custom IDs?**
@@ -196,8 +229,8 @@ await space.createObject({ data: { id: 'article-42', title: 'The Meaning of Life
196
229
  ```typescript
197
230
  // Fire-and-forget: create and reference without waiting
198
231
  const id = RoolClient.generateId();
199
- space.createObject({ data: { id, type: 'note', text: '{{expand this idea}}' } });
200
- space.updateObject(parentId, { data: { notes: [...existingNotes, id] } }); // Add reference immediately
232
+ channel.createObject({ data: { id, type: 'note', text: '{{expand this idea}}' } });
233
+ channel.updateObject(parentId, { data: { notes: [...existingNotes, id] } }); // Add reference immediately
201
234
  ```
202
235
 
203
236
  **Constraints:**
@@ -252,7 +285,7 @@ if (!authenticated) {
252
285
  The `prompt()` method is the primary way to invoke the AI agent. The agent has editor-level capabilities — it can create, modify, and delete objects — but cannot see or modify `_`-prefixed fields.
253
286
 
254
287
  ```typescript
255
- const { message, objects } = await space.prompt(
288
+ const { message, objects } = await channel.prompt(
256
289
  "Create a topic node for the solar system, then child nodes for each planet."
257
290
  );
258
291
  console.log(`AI: ${message}`);
@@ -293,31 +326,31 @@ Returns a message (the AI's response) and the list of objects that were created
293
326
 
294
327
  ```typescript
295
328
  // Reorganize existing objects
296
- const { objects } = await space.prompt(
329
+ const { objects } = await channel.prompt(
297
330
  "Group these notes by topic and create a parent node for each group."
298
331
  );
299
332
 
300
333
  // Work with specific objects
301
- const result = await space.prompt(
334
+ const result = await channel.prompt(
302
335
  "Summarize these articles",
303
336
  { objectIds: ['article-1', 'article-2'] }
304
337
  );
305
338
 
306
339
  // Quick question without mutations (fast model + read-only)
307
- const { message } = await space.prompt(
340
+ const { message } = await channel.prompt(
308
341
  "What topics are covered?",
309
342
  { effort: 'QUICK', readOnly: true }
310
343
  );
311
344
 
312
345
  // Complex analysis with extended reasoning
313
- await space.prompt(
346
+ await channel.prompt(
314
347
  "Analyze relationships and reorganize",
315
348
  { effort: 'REASONING' }
316
349
  );
317
350
 
318
351
  // Attach files for the AI to see (File from <input>, Blob, or base64)
319
352
  const file = fileInput.files[0]; // from <input type="file">
320
- await space.prompt(
353
+ await channel.prompt(
321
354
  "Describe what's in this photo and create an object for it",
322
355
  { attachments: [file] }
323
356
  );
@@ -328,7 +361,7 @@ await space.prompt(
328
361
  Use `responseSchema` to get structured JSON instead of a text message:
329
362
 
330
363
  ```typescript
331
- const { message } = await space.prompt("Categorize these items", {
364
+ const { message } = await channel.prompt("Categorize these items", {
332
365
  objectIds: ['item-1', 'item-2', 'item-3'],
333
366
  responseSchema: {
334
367
  type: 'object',
@@ -349,7 +382,7 @@ console.log(result.categories, result.summary);
349
382
  ### Context Flow
350
383
 
351
384
  AI operations automatically receive context:
352
- - **Interaction history** — Previous interactions and their results from this conversation
385
+ - **Interaction history** — Previous interactions and their results from this channel's conversation
353
386
  - **Recently modified objects** — Objects created or changed recently
354
387
  - **Selected objects** — Objects passed via `objectIds` are given primary focus
355
388
 
@@ -369,6 +402,7 @@ if (!user) {
369
402
  }
370
403
 
371
404
  // Add them to the space
405
+ const space = await client.openSpace('space-id');
372
406
  await space.addUser(user.id, 'editor');
373
407
  ```
374
408
 
@@ -383,6 +417,8 @@ await space.addUser(user.id, 'editor');
383
417
 
384
418
  ### Space Collaboration Methods
385
419
 
420
+ These methods are available on `RoolSpace`:
421
+
386
422
  | Method | Description |
387
423
  |--------|-------------|
388
424
  | `listUsers(): Promise<SpaceMember[]>` | List users with access |
@@ -395,6 +431,8 @@ await space.addUser(user.id, 'editor');
395
431
  Enable public URL access to allow anyone with the space URL to access it:
396
432
 
397
433
  ```typescript
434
+ const space = await client.openSpace('space-id');
435
+
398
436
  // Allow anyone with the URL to view
399
437
  await space.setLinkAccess('viewer');
400
438
 
@@ -423,7 +461,7 @@ When a user accesses a space via URL, they're granted the corresponding role (`v
423
461
  When multiple users have a space open, changes sync in real-time. The `source` field in events tells you who made the change:
424
462
 
425
463
  ```typescript
426
- space.on('objectUpdated', ({ objectId, object, source }) => {
464
+ channel.on('objectUpdated', ({ objectId, object, source }) => {
427
465
  if (source === 'remote_user') {
428
466
  // Another user made this change
429
467
  showCollaboratorActivity(object);
@@ -452,14 +490,28 @@ const client = new RoolClient({
452
490
  });
453
491
  ```
454
492
 
455
- ### Space Lifecycle
493
+ ### Space & Channel Lifecycle
456
494
 
457
495
  | Method | Description |
458
496
  |--------|-------------|
459
497
  | `listSpaces(): Promise<RoolSpaceInfo[]>` | List available spaces |
460
- | `openSpace(id, options?): Promise<RoolSpace>` | Open a space for editing. Options: `{ conversationId?: string }` |
461
- | `createSpace(name?, options?): Promise<RoolSpace>` | Create a new space. Options: `{ conversationId?: string }` |
498
+ | `openSpace(spaceId): Promise<RoolSpace>` | Open a space for admin operations (no real-time subscription) |
499
+ | `openChannel(spaceId, conversationId): Promise<RoolChannel>` | Open a channel on a space with a specific conversation |
500
+ | `createSpace(name): Promise<RoolSpace>` | Create a new space, returns admin handle |
462
501
  | `deleteSpace(id): Promise<void>` | Permanently delete a space (cannot be undone) |
502
+ | `importArchive(name, archive): Promise<RoolSpace>` | Import from a zip archive, creating a new space |
503
+
504
+ ### Channel Management
505
+
506
+ Manage channels (conversations) within a space. Available on both the client and space handles:
507
+
508
+ | Method | Description |
509
+ |--------|-------------|
510
+ | `client.renameChannel(spaceId, channelId, name): Promise<void>` | Rename a channel |
511
+ | `client.deleteChannel(spaceId, channelId): Promise<void>` | Delete a channel and its history |
512
+ | `space.getChannels(): ConversationInfo[]` | List channels (from cached snapshot) |
513
+ | `space.deleteChannel(channelId): Promise<void>` | Delete a channel |
514
+ | `channel.rename(name): Promise<void>` | Rename the current channel |
463
515
 
464
516
  ### User Storage
465
517
 
@@ -516,6 +568,9 @@ client.on('authStateChanged', (authenticated: boolean) => void)
516
568
  client.on('spaceAdded', (space: RoolSpaceInfo) => void) // Space created or access granted
517
569
  client.on('spaceRemoved', (spaceId: string) => void) // Space deleted or access revoked
518
570
  client.on('spaceRenamed', (spaceId: string, newName: string) => void)
571
+ client.on('channelCreated', (spaceId: string, channel: ConversationInfo) => void)
572
+ client.on('channelRenamed', (spaceId: string, channelId: string, newName: string) => void)
573
+ client.on('channelDeleted', (spaceId: string, channelId: string) => void)
519
574
  client.on('userStorageChanged', ({ key, value, source }: UserStorageChangedEvent) => void)
520
575
  client.on('connectionStateChanged', (state: 'connected' | 'disconnected' | 'reconnecting') => void)
521
576
  client.on('error', (error: Error, context?: string) => void)
@@ -535,7 +590,36 @@ client.on('spaceRenamed', (id, name) => {
535
590
 
536
591
  ## RoolSpace API
537
592
 
538
- Spaces are first-class objects with built-in undo/redo, event emission, and real-time sync.
593
+ A space is a lightweight admin handle for space-level operations. It does not have a real-time subscription — use channels for live data and object operations.
594
+
595
+ ### Properties
596
+
597
+ | Property | Description |
598
+ |----------|-------------|
599
+ | `id: string` | Space ID |
600
+ | `name: string` | Space name |
601
+ | `role: RoolUserRole` | User's role |
602
+ | `linkAccess: LinkAccess` | URL sharing level |
603
+
604
+ ### Methods
605
+
606
+ | Method | Description |
607
+ |--------|-------------|
608
+ | `openChannel(conversationId): Promise<RoolChannel>` | Open a channel on this space |
609
+ | `rename(newName): Promise<void>` | Rename this space |
610
+ | `delete(): Promise<void>` | Permanently delete this space |
611
+ | `listUsers(): Promise<SpaceMember[]>` | List users with access |
612
+ | `addUser(userId, role): Promise<void>` | Add user to space |
613
+ | `removeUser(userId): Promise<void>` | Remove user from space |
614
+ | `setLinkAccess(linkAccess): Promise<void>` | Set URL sharing level |
615
+ | `getChannels(): ConversationInfo[]` | List channels (from cached snapshot) |
616
+ | `deleteChannel(channelId): Promise<void>` | Delete a channel |
617
+ | `exportArchive(): Promise<Blob>` | Export space as zip archive |
618
+ | `refresh(): Promise<void>` | Refresh space data from server |
619
+
620
+ ## RoolChannel API
621
+
622
+ A channel is a space + conversationId pair. All object operations, AI prompts, and real-time sync go through a channel. The `conversationId` is fixed at open time — to use a different conversation, open a new channel.
539
623
 
540
624
  ### Properties
541
625
 
@@ -546,15 +630,15 @@ Spaces are first-class objects with built-in undo/redo, event emission, and real
546
630
  | `role: RoolUserRole` | User's role (`'owner' \| 'admin' \| 'editor' \| 'viewer'`) |
547
631
  | `linkAccess: LinkAccess` | URL sharing level (`'none' \| 'viewer' \| 'editor'`) |
548
632
  | `userId: string` | Current user's ID |
549
- | `conversationId: string` | ID for interaction history (tracks AI context). Writable — set to switch conversations. |
550
- | `isReadOnly(): boolean` | True if viewer role |
633
+ | `conversationId: string` | Conversation ID (read-only, fixed at open time) |
634
+ | `isReadOnly: boolean` | True if viewer role |
551
635
 
552
636
  ### Lifecycle
553
637
 
554
638
  | Method | Description |
555
639
  |--------|-------------|
556
640
  | `close(): void` | Clean up resources and stop receiving updates |
557
- | `rename(newName): Promise<void>` | Rename the space |
641
+ | `rename(name): Promise<void>` | Rename this channel (conversation) |
558
642
 
559
643
  ### Object Operations
560
644
 
@@ -563,7 +647,7 @@ Objects are plain key/value records. `id` is the only reserved field; everything
563
647
  | Method | Description |
564
648
  |--------|-------------|
565
649
  | `getObject(objectId): Promise<RoolObject \| undefined>` | Get object data, or undefined if not found. |
566
- | `stat(objectId): Promise<RoolObjectStat \| undefined>` | Get object stat (audit info: modifiedAt, modifiedBy, modifiedByName), or undefined if not found. |
650
+ | `stat(objectId): RoolObjectStat \| undefined` | Get object stat (audit info: modifiedAt, modifiedBy, modifiedByName), or undefined if not found. Sync read from local cache. |
567
651
  | `findObjects(options): Promise<{ objects, message }>` | Find objects using structured filters and natural language. Results sorted by modifiedAt (desc by default). |
568
652
  | `getObjectIds(options?): string[]` | Get all object IDs. Sorted by modifiedAt (desc by default). Options: `{ limit?, order? }`. |
569
653
  | `createObject(options): Promise<{ object, message }>` | Create a new object. Returns the object (with AI-filled content) and message. |
@@ -606,17 +690,17 @@ Find objects using structured filters and/or natural language.
606
690
 
607
691
  ```typescript
608
692
  // Exact field matching (no AI, no credits)
609
- const { objects } = await space.findObjects({
693
+ const { objects } = await channel.findObjects({
610
694
  where: { type: 'article', status: 'published' }
611
695
  });
612
696
 
613
697
  // Pure natural language query (AI interprets)
614
- const { objects, message } = await space.findObjects({
698
+ const { objects, message } = await channel.findObjects({
615
699
  prompt: 'articles about space exploration published this year'
616
700
  });
617
701
 
618
702
  // Combined: where narrows the data, prompt queries within it
619
- const { objects } = await space.findObjects({
703
+ const { objects } = await channel.findObjects({
620
704
  where: { type: 'article' },
621
705
  prompt: 'that discuss climate solutions positively',
622
706
  limit: 10
@@ -661,16 +745,16 @@ Media URLs in object fields are visible to AI. Both uploaded and AI-generated me
661
745
 
662
746
  ```typescript
663
747
  // Upload an image
664
- const url = await space.uploadMedia(file);
665
- await space.createObject({ data: { title: 'Photo', image: url } });
748
+ const url = await channel.uploadMedia(file);
749
+ await channel.createObject({ data: { title: 'Photo', image: url } });
666
750
 
667
751
  // Or let AI generate one using a placeholder
668
- await space.createObject({
752
+ await channel.createObject({
669
753
  data: { title: 'Mascot', image: '{{generate an image of a flying tortoise}}' }
670
754
  });
671
755
 
672
756
  // Display media (handles auth automatically)
673
- const response = await space.fetchMedia(object.image);
757
+ const response = await channel.fetchMedia(object.image);
674
758
  if (response.contentType.startsWith('image/')) {
675
759
  const blob = await response.blob();
676
760
  img.src = URL.createObjectURL(blob);
@@ -684,7 +768,7 @@ Collections are types you can use to group objects in a space. Every object must
684
768
 
685
769
  ```typescript
686
770
  // Define a collection with typed fields
687
- await space.createCollection('article', [
771
+ await channel.createCollection('article', [
688
772
  { name: 'title', type: { kind: 'string' } },
689
773
  { name: 'status', type: { kind: 'enum', values: ['draft', 'published', 'archived'] } },
690
774
  { name: 'tags', type: { kind: 'array', inner: { kind: 'string' } } },
@@ -692,11 +776,11 @@ await space.createCollection('article', [
692
776
  ]);
693
777
 
694
778
  // Read the current schema
695
- const schema = space.getSchema();
779
+ const schema = channel.getSchema();
696
780
  console.log(schema.article.fields); // FieldDef[]
697
781
 
698
782
  // Modify an existing collection's fields
699
- await space.alterCollection('article', [
783
+ await channel.alterCollection('article', [
700
784
  { name: 'title', type: { kind: 'string' } },
701
785
  { name: 'status', type: { kind: 'enum', values: ['draft', 'review', 'published', 'archived'] } },
702
786
  { name: 'tags', type: { kind: 'array', inner: { kind: 'string' } } },
@@ -705,7 +789,7 @@ await space.alterCollection('article', [
705
789
  ]);
706
790
 
707
791
  // Remove a collection
708
- await space.dropCollection('article');
792
+ await channel.dropCollection('article');
709
793
  ```
710
794
 
711
795
  | Method | Description |
@@ -739,6 +823,7 @@ Export and import space data as zip archives for backup, portability, or migrati
739
823
 
740
824
  **Export:**
741
825
  ```typescript
826
+ const space = await client.openSpace('space-id');
742
827
  const archive = await space.exportArchive();
743
828
  // Save as .zip file
744
829
  const url = URL.createObjectURL(archive);
@@ -746,12 +831,13 @@ const url = URL.createObjectURL(archive);
746
831
 
747
832
  **Import:**
748
833
  ```typescript
749
- const newSpace = await client.importArchive('Imported Data', archiveBlob);
834
+ const space = await client.importArchive('Imported Data', archiveBlob);
835
+ const channel = await space.openChannel('main');
750
836
  ```
751
837
 
752
838
  The archive format bundles `data.json` (with objects, metadata, and conversations) and a `media/` folder containing all media files. Media URLs are rewritten to relative paths within the archive and restored on import.
753
839
 
754
- ### Space Events
840
+ ### Channel Events
755
841
 
756
842
  Semantic events describe what changed. Events fire for both local changes and remote changes.
757
843
 
@@ -763,27 +849,21 @@ Semantic events describe what changed. Events fire for both local changes and re
763
849
  // - 'system': Resync after error
764
850
 
765
851
  // Object events
766
- space.on('objectCreated', ({ objectId, object, source }) => void)
767
- space.on('objectUpdated', ({ objectId, object, source }) => void)
768
- space.on('objectDeleted', ({ objectId, source }) => void)
852
+ channel.on('objectCreated', ({ objectId, object, source }) => void)
853
+ channel.on('objectUpdated', ({ objectId, object, source }) => void)
854
+ channel.on('objectDeleted', ({ objectId, source }) => void)
769
855
 
770
856
  // Space metadata
771
- space.on('metadataUpdated', ({ metadata, source }) => void)
857
+ channel.on('metadataUpdated', ({ metadata, source }) => void)
772
858
 
773
859
  // Conversation updated (fetch with getInteractions())
774
- space.on('conversationUpdated', ({ conversationId, source }) => void)
775
-
776
- // Conversation list changed (created, deleted, renamed)
777
- space.on('conversationsChanged', ({ action, conversationId, name, source }) => void)
860
+ channel.on('conversationUpdated', ({ conversationId, source }) => void)
778
861
 
779
862
  // Full state replacement (undo/redo, resync after error)
780
- space.on('reset', ({ source }) => void)
863
+ channel.on('reset', ({ source }) => void)
781
864
 
782
- // ConversationId was changed on the space
783
- space.on('conversationIdChanged', ({ previousConversationId, newConversationId }) => void)
784
-
785
- // Sync error occurred, space resynced from server
786
- space.on('syncError', (error: Error) => void)
865
+ // Sync error occurred, channel resynced from server
866
+ channel.on('syncError', (error: Error) => void)
787
867
  ```
788
868
 
789
869
  ### Error Handling
@@ -792,7 +872,7 @@ AI operations may fail due to rate limiting or other transient errors. Check `er
792
872
 
793
873
  ```typescript
794
874
  try {
795
- await space.updateObject(objectId, { prompt: 'expand this' });
875
+ await channel.updateObject(objectId, { prompt: 'expand this' });
796
876
  } catch (error) {
797
877
  if (error.message.includes('temporarily unavailable')) {
798
878
  showToast('Service busy, please try again in a moment');
@@ -804,13 +884,13 @@ try {
804
884
 
805
885
  ## Interaction History
806
886
 
807
- Each `RoolSpace` instance has a `conversationId` that tracks interaction history for that space. The history records all meaningful interactions (prompts, object mutations) as self-contained entries, each capturing the request and its result. History is stored in the space data itself and syncs in real-time to all clients.
887
+ Each channel has a `conversationId` that identifies its conversation. The history records all meaningful interactions (prompts, object mutations) as self-contained entries, each capturing the request and its result. History is stored in the space data itself and syncs in real-time to all clients.
808
888
 
809
889
  ### What the AI Receives
810
890
 
811
891
  AI operations (`prompt`, `createObject`, `updateObject`, `findObjects`) automatically receive:
812
892
 
813
- - **Interaction history** — Previous interactions and their results from this conversation
893
+ - **Interaction history** — Previous interactions and their results from this channel's conversation
814
894
  - **Recently modified objects** — Objects in the space recently created or changed
815
895
  - **Selected objects** — Objects passed via `objectIds` are given primary focus
816
896
 
@@ -819,31 +899,20 @@ This context flows automatically — no configuration needed. The AI sees enough
819
899
  ### Accessing History
820
900
 
821
901
  ```typescript
822
- // Get interactions for the current conversationId
823
- const interactions = space.getInteractions();
824
- // Returns: Interaction[]
825
-
826
- // Get interactions for a specific conversation ID
827
- const interactions = space.getInteractionsById('other-conversation-id');
902
+ // Get interactions for this channel's conversation
903
+ const interactions = channel.getInteractions();
828
904
  // Returns: Interaction[]
829
-
830
- // List all conversation IDs that have interactions
831
- const conversationIds = space.getConversationIds();
832
- // Returns: string[]
833
905
  ```
834
906
 
835
- ### Conversation Access Methods
907
+ ### Conversation Methods
836
908
 
837
909
  | Method | Description |
838
910
  |--------|-------------|
839
- | `getInteractions(): Interaction[]` | Get interactions for the current conversationId |
840
- | `getInteractionsById(id): Interaction[]` | Get interactions for a specific conversation ID |
841
- | `getConversationIds(): string[]` | List all conversation IDs that have conversations |
842
- | `deleteConversation(conversationId?): Promise<void>` | Delete a conversation and its history. Defaults to current conversation. |
843
- | `renameConversation(id, name): Promise<void>` | Rename a conversation. Creates it if it doesn't exist. |
844
- | `listConversations(): ConversationInfo[]` | List all conversations with summary info. |
845
- | `getSystemInstruction(): string \| undefined` | Get system instruction for current conversation. |
846
- | `setSystemInstruction(instruction): Promise<void>` | Set system instruction for current conversation. Pass `null` to clear. |
911
+ | `getInteractions(): Interaction[]` | Get interactions for this channel's conversation |
912
+ | `getSystemInstruction(): string \| undefined` | Get system instruction for this conversation |
913
+ | `setSystemInstruction(instruction): Promise<void>` | Set system instruction for this conversation. Pass `null` to clear. |
914
+
915
+ Channel management (listing, renaming, deleting channels) is done via the client see [Channel Management](#channel-management).
847
916
 
848
917
  ### System Instructions
849
918
 
@@ -851,18 +920,18 @@ System instructions customize how the AI behaves within a conversation. The inst
851
920
 
852
921
  ```typescript
853
922
  // Make the AI behave like an SQL interpreter
854
- await space.setSystemInstruction(
923
+ await channel.setSystemInstruction(
855
924
  'Behave like an intelligent SQL interpreter. Respond with simple markdown tables. ' +
856
925
  'Translate the objects in the space to the implied structure in your responses.'
857
926
  );
858
927
 
859
928
  // Now prompts are interpreted as SQL-like queries
860
- const { message } = await space.prompt('SELECT task, due_date FROM tasks ORDER BY due_date');
929
+ const { message } = await channel.prompt('SELECT task, due_date FROM tasks ORDER BY due_date');
861
930
  // Returns a markdown table of tasks, even if no "tasks" objects exist -
862
931
  // the AI infers actual tasks from the space content
863
932
 
864
933
  // Clear the instruction to return to default behavior
865
- await space.setSystemInstruction(null);
934
+ await channel.setSystemInstruction(null);
866
935
  ```
867
936
 
868
937
  System instructions are useful for:
@@ -874,56 +943,37 @@ System instructions are useful for:
874
943
  ### Listening for Updates
875
944
 
876
945
  ```typescript
877
- space.on('conversationUpdated', ({ conversationId, source }) => {
946
+ channel.on('conversationUpdated', ({ conversationId, source }) => {
878
947
  // Conversation changed - refresh if needed
879
- const interactions = space.getInteractions();
948
+ const interactions = channel.getInteractions();
880
949
  renderInteractions(interactions);
881
950
  });
882
951
  ```
883
952
 
884
- ### Conversation Isolation
885
-
886
- By default, each call to `openSpace()` or `createSpace()` generates a new `conversationId`. This means:
887
- - Opening a space twice gives you two independent AI conversation histories
888
- - Closing and reopening a space starts fresh
953
+ ### Multiple Conversations
889
954
 
890
- ### Switching Conversations
891
-
892
- Set `conversationId` to switch conversations without reopening the space:
955
+ Each channel is bound to a single conversation. To work with multiple conversations on the same space, open multiple channels:
893
956
 
894
957
  ```typescript
895
- const space = await client.openSpace('abc123');
896
-
897
- // User clicks "Research" thread in sidebar
898
- space.conversationId = 'research-thread';
899
- await space.prompt("Analyze this data");
900
-
901
- // User clicks "Main" thread
902
- space.conversationId = 'main-thread';
903
- await space.prompt("Summarize findings");
958
+ // Open two channels on the same space with different conversations
959
+ const research = await client.openChannel('space-id', 'research');
960
+ const main = await client.openChannel('space-id', 'main');
904
961
 
905
- // Listen for conversation switches
906
- space.on('conversationIdChanged', ({ previousConversationId, newConversationId }) => {
907
- // Re-render chat UI with new conversation's history
908
- renderChat(space.getInteractions());
909
- });
910
- ```
911
-
912
- ### Resuming Conversations
962
+ // Each has independent history
963
+ await research.prompt("Analyze this data");
964
+ await main.prompt("Summarize findings");
913
965
 
914
- Pass a `conversationId` at open time to start with a specific conversation:
915
-
916
- ```typescript
917
- // Resume a known conversation when opening
918
- const space = await client.openSpace('abc123', { conversationId: 'research-thread' });
966
+ // Close when done
967
+ research.close();
968
+ main.close();
919
969
  ```
920
970
 
921
971
  **Use cases:**
922
- - **Page refresh** — Store `conversationId` in localStorage to maintain context across reloads
923
- - **Multiple conversations** — Switch between different conversation contexts using the setter
924
- - **Collaborative conversations** — Share a `conversationId` between users to enable shared AI conversation history
972
+ - **Chat app with sidebar** — Each sidebar entry is a channel with a different conversationId
973
+ - **Page refresh** — Store the conversationId in localStorage to resume the same conversation
974
+ - **Collaborative conversations** — Share a conversationId between users to enable shared AI conversation history
925
975
 
926
- **Tip:** Use the user's id as `conversationId` to share context across tabs/devices, or a fixed string like `'shared'` to share context across all users.
976
+ **Tip:** Use the user's id as conversationId to share context across tabs/devices, or a fixed string like `'shared'` to share context across all users.
927
977
 
928
978
  Note: Interaction history is truncated to the most recent 50 entries to manage space size.
929
979
 
@@ -978,7 +1028,7 @@ interface RoolObject {
978
1028
  [key: string]: unknown;
979
1029
  }
980
1030
 
981
- // Object stat - audit information returned by space.stat()
1031
+ // Object stat - audit information returned by channel.stat()
982
1032
  interface RoolObjectStat {
983
1033
  modifiedAt: number;
984
1034
  modifiedBy: string;
@@ -999,7 +1049,7 @@ interface Conversation {
999
1049
  interactions: Interaction[]; // Interaction history
1000
1050
  }
1001
1051
 
1002
- // Conversation summary info (returned by listConversations)
1052
+ // Conversation summary info (returned by client.getChannels)
1003
1053
  interface ConversationInfo {
1004
1054
  id: string;
1005
1055
  name: string | null;
@@ -1008,7 +1058,6 @@ interface ConversationInfo {
1008
1058
  createdByName: string | null;
1009
1059
  interactionCount: number;
1010
1060
  }
1011
-
1012
1061
  ```
1013
1062
 
1014
1063
  ### Interaction Types
@@ -1070,7 +1119,7 @@ interface PromptOptions {
1070
1119
  A Rool Space is a persistent, shared world model. Applications project different interaction patterns onto the same core primitives:
1071
1120
 
1072
1121
  - **Objects** store durable state, with references to other objects via data fields
1073
- - **Interaction history** tracks what happened (requests, results, modified objects)
1122
+ - **Channels** provide independent AI conversation contexts over shared objects
1074
1123
  - **Events** describe what changed in real-time
1075
1124
 
1076
1125
  Below are a few representative patterns.
@@ -1078,12 +1127,12 @@ Below are a few representative patterns.
1078
1127
  ### Chat With Generated Artifacts
1079
1128
 
1080
1129
  - **Space**: documents, notes, images, tasks as objects
1081
- - **Interaction history**: prompts and AI responses stored in space, synced across clients
1130
+ - **Channels**: each chat thread is a separate channel on the same space
1082
1131
  - **UI**: renders interactions from `getInteractions()` as chat; derives artifact lists from object events
1083
1132
 
1084
1133
  **Pattern**
1085
1134
  - Interaction history syncs in real-time; UI renders entries as chat bubbles
1086
- - Artifacts are persistent objects
1135
+ - Artifacts are persistent objects shared across all channels
1087
1136
  - Listen to `conversationUpdated` to update chat UI
1088
1137
  - Selecting objects defines the AI working set via `objectIds`
1089
1138
 
@@ -1091,7 +1140,7 @@ Below are a few representative patterns.
1091
1140
 
1092
1141
  - **Space**: rooms, items, NPCs, players as objects
1093
1142
  - **References**: navigation, containment, location via data fields
1094
- - **Conversation**: player commands and narrative continuity
1143
+ - **Channel**: player commands and narrative continuity
1095
1144
 
1096
1145
  **Pattern**
1097
1146
  - The space is the shared world state
@@ -1102,7 +1151,7 @@ Below are a few representative patterns.
1102
1151
 
1103
1152
  - **Space**: concepts, sources, hypotheses as objects
1104
1153
  - **References**: semantic connections between objects via data fields
1105
- - **Conversation**: exploratory analysis and questioning
1154
+ - **Channel**: exploratory analysis and questioning
1106
1155
 
1107
1156
  **Pattern**
1108
1157
  - Graph structure lives in object data fields containing other object IDs
@@ -1113,7 +1162,7 @@ Below are a few representative patterns.
1113
1162
 
1114
1163
  - Durable content lives in space objects
1115
1164
  - References between objects are data fields whose values are object IDs
1116
- - Interaction history lives in space conversations (persistent, synced, truncated to 50 entries)
1165
+ - Interaction history lives in channels (persistent, synced, truncated to 50 entries)
1117
1166
  - UI state lives in the client, space metadata, or `_`-prefixed fields
1118
1167
  - AI focus is controlled by object selection, not by replaying history
1119
1168