@positronic/template-new-project 0.0.68 → 0.0.70
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/index.js +4 -4
- package/package.json +1 -1
- package/template/.positronic/src/index.ts +2 -1
- package/template/.positronic/wrangler.jsonc +8 -2
- package/template/brain.ts +4 -1
- package/template/docs/brain-dsl-guide.md +99 -1
- package/template/docs/memory-guide.md +292 -0
- package/template/docs/positronic-guide.md +51 -0
package/index.js
CHANGED
|
@@ -53,10 +53,10 @@ module.exports = {
|
|
|
53
53
|
],
|
|
54
54
|
setup: async ctx => {
|
|
55
55
|
const devRootPath = process.env.POSITRONIC_LOCAL_PATH;
|
|
56
|
-
let coreVersion = '^0.0.
|
|
57
|
-
let cloudflareVersion = '^0.0.
|
|
58
|
-
let clientVercelVersion = '^0.0.
|
|
59
|
-
let genUIComponentsVersion = '^0.0.
|
|
56
|
+
let coreVersion = '^0.0.70';
|
|
57
|
+
let cloudflareVersion = '^0.0.70';
|
|
58
|
+
let clientVercelVersion = '^0.0.70';
|
|
59
|
+
let genUIComponentsVersion = '^0.0.70';
|
|
60
60
|
|
|
61
61
|
// Map backend selection to package names
|
|
62
62
|
const backendPackageMap = {
|
package/package.json
CHANGED
|
@@ -7,6 +7,7 @@ import {
|
|
|
7
7
|
MonitorDO,
|
|
8
8
|
ScheduleDO,
|
|
9
9
|
GovernorDO,
|
|
10
|
+
AuthDO,
|
|
10
11
|
PositronicManifest,
|
|
11
12
|
} from "@positronic/cloudflare";
|
|
12
13
|
// Import the generated manifests - NOTE the .js extension for runtime compatibility
|
|
@@ -38,4 +39,4 @@ export default {
|
|
|
38
39
|
fetch: app.fetch,
|
|
39
40
|
} satisfies ExportedHandler<Env>;
|
|
40
41
|
|
|
41
|
-
export { BrainRunnerDO, MonitorDO, ScheduleDO, GovernorDO };
|
|
42
|
+
export { BrainRunnerDO, MonitorDO, ScheduleDO, GovernorDO, AuthDO };
|
|
@@ -17,6 +17,10 @@
|
|
|
17
17
|
{
|
|
18
18
|
"tag": "v2",
|
|
19
19
|
"new_sqlite_classes": ["GovernorDO"]
|
|
20
|
+
},
|
|
21
|
+
{
|
|
22
|
+
"tag": "v3",
|
|
23
|
+
"new_sqlite_classes": ["AuthDO"]
|
|
20
24
|
}
|
|
21
25
|
],
|
|
22
26
|
"durable_objects": {
|
|
@@ -24,7 +28,8 @@
|
|
|
24
28
|
{ "name": "BRAIN_RUNNER_DO", "class_name": "BrainRunnerDO" },
|
|
25
29
|
{ "name": "MONITOR_DO", "class_name": "MonitorDO" },
|
|
26
30
|
{ "name": "SCHEDULE_DO", "class_name": "ScheduleDO" },
|
|
27
|
-
{ "name": "GOVERNOR_DO", "class_name": "GovernorDO" }
|
|
31
|
+
{ "name": "GOVERNOR_DO", "class_name": "GovernorDO" },
|
|
32
|
+
{ "name": "AUTH_DO", "class_name": "AuthDO" }
|
|
28
33
|
]
|
|
29
34
|
},
|
|
30
35
|
"r2_buckets": [
|
|
@@ -65,7 +70,8 @@
|
|
|
65
70
|
{ "name": "BRAIN_RUNNER_DO", "class_name": "BrainRunnerDO" },
|
|
66
71
|
{ "name": "MONITOR_DO", "class_name": "MonitorDO" },
|
|
67
72
|
{ "name": "SCHEDULE_DO", "class_name": "ScheduleDO" },
|
|
68
|
-
{ "name": "GOVERNOR_DO", "class_name": "GovernorDO" }
|
|
73
|
+
{ "name": "GOVERNOR_DO", "class_name": "GovernorDO" },
|
|
74
|
+
{ "name": "AUTH_DO", "class_name": "AuthDO" }
|
|
69
75
|
]
|
|
70
76
|
},
|
|
71
77
|
"r2_buckets": [
|
package/template/brain.ts
CHANGED
|
@@ -81,7 +81,10 @@ import { components } from './components/index.js';
|
|
|
81
81
|
* const memoryTools = createMem0Tools();
|
|
82
82
|
* ```
|
|
83
83
|
*
|
|
84
|
-
*
|
|
84
|
+
* Memory is automatically scoped to the current user (via currentUser.id)
|
|
85
|
+
* and the brain name. No need to pass userId manually.
|
|
86
|
+
*
|
|
87
|
+
* See docs/memory-guide.md for full details.
|
|
85
88
|
*/
|
|
86
89
|
export const brain = createBrain({
|
|
87
90
|
components,
|
|
@@ -642,6 +642,101 @@ const brainWithComponents = brain('Custom UI Brain')
|
|
|
642
642
|
});
|
|
643
643
|
```
|
|
644
644
|
|
|
645
|
+
### Typed Store with `withStore()`
|
|
646
|
+
|
|
647
|
+
The `withStore()` method declares a typed key-value store for persistent structured data. Unlike brain state (which resets each run), store data persists across runs.
|
|
648
|
+
|
|
649
|
+
#### Declaring Store Shape
|
|
650
|
+
|
|
651
|
+
Use Zod types to declare the shape of your store. All store keys are scoped per-brain — each brain gets its own namespace automatically.
|
|
652
|
+
|
|
653
|
+
```typescript
|
|
654
|
+
import { z } from 'zod';
|
|
655
|
+
|
|
656
|
+
const myBrain = brain('email-preferences')
|
|
657
|
+
.withStore({
|
|
658
|
+
deselectedThreads: z.array(z.string()),
|
|
659
|
+
lastProcessedAt: z.number(),
|
|
660
|
+
})
|
|
661
|
+
.step('Process', async ({ store }) => {
|
|
662
|
+
// Typed get — returns the value or undefined if not set
|
|
663
|
+
const deselected = await store.get('deselectedThreads') ?? [];
|
|
664
|
+
const lastTime = await store.get('lastProcessedAt');
|
|
665
|
+
|
|
666
|
+
// Typed set
|
|
667
|
+
await store.set('lastProcessedAt', Date.now());
|
|
668
|
+
|
|
669
|
+
return { deselected, lastTime };
|
|
670
|
+
});
|
|
671
|
+
```
|
|
672
|
+
|
|
673
|
+
#### Per-User Fields
|
|
674
|
+
|
|
675
|
+
Mark fields as per-user to scope them to the current user. This is useful for user preferences, user-specific state, or any data that should be isolated between users.
|
|
676
|
+
|
|
677
|
+
```typescript
|
|
678
|
+
const myBrain = brain('dashboard')
|
|
679
|
+
.withStore({
|
|
680
|
+
globalConfig: z.object({ theme: z.string() }), // shared across all users
|
|
681
|
+
userPreferences: { type: z.object({ darkMode: z.boolean() }), perUser: true }, // per-user
|
|
682
|
+
})
|
|
683
|
+
.step('Load Preferences', async ({ store }) => {
|
|
684
|
+
const config = await store.get('globalConfig');
|
|
685
|
+
const prefs = await store.get('userPreferences'); // scoped to currentUser automatically
|
|
686
|
+
return { config, prefs };
|
|
687
|
+
});
|
|
688
|
+
```
|
|
689
|
+
|
|
690
|
+
Per-user fields require a `currentUser` to be set when running the brain. If a per-user field is accessed without a current user, an error is thrown.
|
|
691
|
+
|
|
692
|
+
#### Store Scoping
|
|
693
|
+
|
|
694
|
+
All store keys are automatically namespaced:
|
|
695
|
+
|
|
696
|
+
- **Shared fields**: scoped per-brain (e.g., brain "my-brain" key "counter" is isolated from brain "other-brain" key "counter")
|
|
697
|
+
- **Per-user fields**: scoped per-brain AND per-user (each user gets their own value)
|
|
698
|
+
|
|
699
|
+
There is no global scope — every field belongs to a specific brain.
|
|
700
|
+
|
|
701
|
+
#### Store Operations
|
|
702
|
+
|
|
703
|
+
The store provides four operations:
|
|
704
|
+
|
|
705
|
+
```typescript
|
|
706
|
+
await store.get('key'); // Returns T | undefined
|
|
707
|
+
await store.set('key', value); // Sets the value
|
|
708
|
+
await store.delete('key'); // Removes the key
|
|
709
|
+
await store.has('key'); // Returns boolean
|
|
710
|
+
```
|
|
711
|
+
|
|
712
|
+
#### Using with `createBrain()`
|
|
713
|
+
|
|
714
|
+
You can declare store fields at the project level so all brains share the same store shape:
|
|
715
|
+
|
|
716
|
+
```typescript
|
|
717
|
+
// brain.ts
|
|
718
|
+
export const brain = createBrain({
|
|
719
|
+
services: { slack },
|
|
720
|
+
store: {
|
|
721
|
+
processedCount: z.number(),
|
|
722
|
+
userSettings: { type: z.object({ notifications: z.boolean() }), perUser: true },
|
|
723
|
+
},
|
|
724
|
+
});
|
|
725
|
+
```
|
|
726
|
+
|
|
727
|
+
Or declare per-brain stores for brain-specific data:
|
|
728
|
+
|
|
729
|
+
```typescript
|
|
730
|
+
// brains/my-brain.ts
|
|
731
|
+
export default brain('my-brain')
|
|
732
|
+
.withStore({ counter: z.number() })
|
|
733
|
+
.step('Increment', async ({ store }) => {
|
|
734
|
+
const count = await store.get('counter') ?? 0;
|
|
735
|
+
await store.set('counter', count + 1);
|
|
736
|
+
return { count: count + 1 };
|
|
737
|
+
});
|
|
738
|
+
```
|
|
739
|
+
|
|
645
740
|
### Using `createBrain()` for Project Configuration
|
|
646
741
|
|
|
647
742
|
For project-wide configuration, use `createBrain()` in your `brain.ts` file:
|
|
@@ -669,11 +764,14 @@ export const brain = createBrain({
|
|
|
669
764
|
props: z.object({ message: z.string(), type: z.enum(['info', 'warning', 'error']) }),
|
|
670
765
|
render: (props) => `<div class="alert alert-<%= '${props.type}' %>"><%= '${props.message}' %></div>`
|
|
671
766
|
}
|
|
767
|
+
},
|
|
768
|
+
store: {
|
|
769
|
+
processedCount: z.number(),
|
|
672
770
|
}
|
|
673
771
|
});
|
|
674
772
|
```
|
|
675
773
|
|
|
676
|
-
All brains created with this factory will have access to the configured services, tools, and
|
|
774
|
+
All brains created with this factory will have access to the configured services, tools, components, and store.
|
|
677
775
|
|
|
678
776
|
## Running Brains
|
|
679
777
|
|
|
@@ -0,0 +1,292 @@
|
|
|
1
|
+
# Memory Guide
|
|
2
|
+
|
|
3
|
+
This guide covers the memory system in Positronic, which enables brains to store and retrieve long-term memories using [Mem0](https://mem0.ai) or other memory providers.
|
|
4
|
+
|
|
5
|
+
## Overview
|
|
6
|
+
|
|
7
|
+
The memory system provides:
|
|
8
|
+
- **Long-term memory storage** - Persist facts, preferences, and context across brain runs
|
|
9
|
+
- **Semantic search** - Retrieve relevant memories based on natural language queries
|
|
10
|
+
- **Automatic conversation indexing** - Optionally store all conversations for later retrieval
|
|
11
|
+
- **Tools for agents** - Built-in tools that let agents store and recall memories
|
|
12
|
+
- **Automatic user scoping** - Memories are scoped to the current user via `currentUser`, no manual userId threading needed
|
|
13
|
+
|
|
14
|
+
## Quick Start
|
|
15
|
+
|
|
16
|
+
### 1. Install the package
|
|
17
|
+
|
|
18
|
+
```bash
|
|
19
|
+
npm install @positronic/mem0
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
### 2. Set up the provider
|
|
23
|
+
|
|
24
|
+
Add your Mem0 API key to `.env`:
|
|
25
|
+
|
|
26
|
+
```bash
|
|
27
|
+
MEM0_API_KEY=your-api-key-here
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
### 3. Configure in brain.ts
|
|
31
|
+
|
|
32
|
+
```typescript
|
|
33
|
+
import { createBrain, defaultTools } from '@positronic/core';
|
|
34
|
+
import { createMem0Provider, createMem0Tools } from '@positronic/mem0';
|
|
35
|
+
import { components } from './components/index.js';
|
|
36
|
+
|
|
37
|
+
const memory = createMem0Provider({
|
|
38
|
+
apiKey: process.env.MEM0_API_KEY!,
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
export const brain = createBrain({
|
|
42
|
+
components,
|
|
43
|
+
defaultTools,
|
|
44
|
+
memory,
|
|
45
|
+
});
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
### 4. Use memory tools in agents
|
|
49
|
+
|
|
50
|
+
```typescript
|
|
51
|
+
import { brain } from '../brain.js';
|
|
52
|
+
import { createMem0Tools } from '@positronic/mem0';
|
|
53
|
+
import { z } from 'zod';
|
|
54
|
+
|
|
55
|
+
const memoryTools = createMem0Tools();
|
|
56
|
+
|
|
57
|
+
export default brain('assistant')
|
|
58
|
+
.brain('Help User', () => ({
|
|
59
|
+
system: 'You are helpful. Use rememberFact to store user preferences.',
|
|
60
|
+
prompt: 'The user said: I prefer dark mode',
|
|
61
|
+
tools: {
|
|
62
|
+
...memoryTools,
|
|
63
|
+
done: {
|
|
64
|
+
description: 'Complete the task',
|
|
65
|
+
inputSchema: z.object({ result: z.string() }),
|
|
66
|
+
terminal: true,
|
|
67
|
+
},
|
|
68
|
+
},
|
|
69
|
+
}));
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
## Memory Tools
|
|
73
|
+
|
|
74
|
+
The package provides two tools that agents can use:
|
|
75
|
+
|
|
76
|
+
### rememberFact
|
|
77
|
+
|
|
78
|
+
Stores a fact in long-term memory.
|
|
79
|
+
|
|
80
|
+
- **Input**: `{ fact: string }`
|
|
81
|
+
- **Output**: `{ remembered: boolean, fact: string }`
|
|
82
|
+
|
|
83
|
+
When the agent calls `rememberFact({ fact: "User prefers dark mode" })`, the fact is stored in Mem0 and can be retrieved later.
|
|
84
|
+
|
|
85
|
+
### recallMemories
|
|
86
|
+
|
|
87
|
+
Searches for relevant memories.
|
|
88
|
+
|
|
89
|
+
- **Input**: `{ query: string, limit?: number }`
|
|
90
|
+
- **Output**: `{ found: number, memories: Array<{ content: string, relevance?: number }> }`
|
|
91
|
+
|
|
92
|
+
When the agent calls `recallMemories({ query: "user preferences" })`, it receives matching memories with relevance scores.
|
|
93
|
+
|
|
94
|
+
### Using Memory Tools in Agents
|
|
95
|
+
|
|
96
|
+
```typescript
|
|
97
|
+
import { brain } from '../brain.js';
|
|
98
|
+
import { createMem0Tools } from '@positronic/mem0';
|
|
99
|
+
import { z } from 'zod';
|
|
100
|
+
|
|
101
|
+
const memoryTools = createMem0Tools();
|
|
102
|
+
|
|
103
|
+
export default brain('personalized-assistant')
|
|
104
|
+
.brain('Chat', () => ({
|
|
105
|
+
system: `You are a personalized assistant.
|
|
106
|
+
|
|
107
|
+
Use rememberFact to store important information about the user:
|
|
108
|
+
- Preferences (theme, communication style, etc.)
|
|
109
|
+
- Context (current projects, goals)
|
|
110
|
+
- Any facts they want you to remember
|
|
111
|
+
|
|
112
|
+
Use recallMemories before responding to check for relevant context.`,
|
|
113
|
+
prompt: userMessage,
|
|
114
|
+
tools: {
|
|
115
|
+
...memoryTools,
|
|
116
|
+
done: {
|
|
117
|
+
description: 'Send final response',
|
|
118
|
+
inputSchema: z.object({ response: z.string() }),
|
|
119
|
+
terminal: true,
|
|
120
|
+
},
|
|
121
|
+
},
|
|
122
|
+
}));
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
## Automatic Conversation Indexing
|
|
126
|
+
|
|
127
|
+
The Mem0 adapter automatically stores all agent conversations to memory. This builds up context over time without explicit tool calls.
|
|
128
|
+
|
|
129
|
+
### Setting Up the Adapter
|
|
130
|
+
|
|
131
|
+
In your `runner.ts`:
|
|
132
|
+
|
|
133
|
+
```typescript
|
|
134
|
+
import { BrainRunner } from '@positronic/core';
|
|
135
|
+
import { createMem0Adapter, createMem0Provider } from '@positronic/mem0';
|
|
136
|
+
|
|
137
|
+
const provider = createMem0Provider({
|
|
138
|
+
apiKey: process.env.MEM0_API_KEY!,
|
|
139
|
+
});
|
|
140
|
+
|
|
141
|
+
const adapter = createMem0Adapter({ provider });
|
|
142
|
+
|
|
143
|
+
export const runner = new BrainRunner({
|
|
144
|
+
adapters: [adapter],
|
|
145
|
+
client: myClient,
|
|
146
|
+
});
|
|
147
|
+
```
|
|
148
|
+
|
|
149
|
+
### Adapter Behavior
|
|
150
|
+
|
|
151
|
+
- **On agent start**: Buffers the initial prompt as a user message
|
|
152
|
+
- **During execution**: Buffers all user and assistant messages
|
|
153
|
+
- **On completion**: Flushes buffer to memory provider
|
|
154
|
+
- **On error/cancel**: Discards buffer (doesn't store failed conversations)
|
|
155
|
+
|
|
156
|
+
### Including Tool Calls
|
|
157
|
+
|
|
158
|
+
By default, tool calls are not included in the indexed conversation. Enable this for full conversation history:
|
|
159
|
+
|
|
160
|
+
```typescript
|
|
161
|
+
const adapter = createMem0Adapter({
|
|
162
|
+
provider,
|
|
163
|
+
includeToolCalls: true,
|
|
164
|
+
});
|
|
165
|
+
```
|
|
166
|
+
|
|
167
|
+
## Accessing Memory in Steps
|
|
168
|
+
|
|
169
|
+
When memory is attached, you can access it directly in step functions:
|
|
170
|
+
|
|
171
|
+
### In Regular Steps
|
|
172
|
+
|
|
173
|
+
```typescript
|
|
174
|
+
export default brain('my-brain')
|
|
175
|
+
.step('Load Context', async ({ memory }) => {
|
|
176
|
+
const memories = await memory.search('user preferences', {
|
|
177
|
+
limit: 5,
|
|
178
|
+
});
|
|
179
|
+
|
|
180
|
+
return {
|
|
181
|
+
context: memories.map(m => m.content).join('\n'),
|
|
182
|
+
};
|
|
183
|
+
});
|
|
184
|
+
```
|
|
185
|
+
|
|
186
|
+
### In Agent Config Functions
|
|
187
|
+
|
|
188
|
+
```typescript
|
|
189
|
+
export default brain('my-brain')
|
|
190
|
+
.brain('Process', async ({ memory }) => {
|
|
191
|
+
const prefs = await memory.search('user preferences');
|
|
192
|
+
|
|
193
|
+
const context = prefs.length > 0
|
|
194
|
+
? '\n\nUser preferences:\n' + prefs.map(p => '- ' + p.content).join('\n')
|
|
195
|
+
: '';
|
|
196
|
+
|
|
197
|
+
return {
|
|
198
|
+
system: 'You are helpful.' + context,
|
|
199
|
+
prompt: 'Help the user with their request',
|
|
200
|
+
tools: { /* ... */ },
|
|
201
|
+
};
|
|
202
|
+
});
|
|
203
|
+
```
|
|
204
|
+
|
|
205
|
+
## Helper Functions
|
|
206
|
+
|
|
207
|
+
The package includes helper functions for common memory patterns:
|
|
208
|
+
|
|
209
|
+
### formatMemories
|
|
210
|
+
|
|
211
|
+
Formats an array of memories into a readable string:
|
|
212
|
+
|
|
213
|
+
```typescript
|
|
214
|
+
import { formatMemories } from '@positronic/mem0';
|
|
215
|
+
|
|
216
|
+
const memories = await memory.search('preferences');
|
|
217
|
+
|
|
218
|
+
const text = formatMemories(memories);
|
|
219
|
+
// "1. User prefers dark mode\n2. User likes concise responses"
|
|
220
|
+
|
|
221
|
+
const formatted = formatMemories(memories, {
|
|
222
|
+
header: 'Known preferences:',
|
|
223
|
+
includeScores: true,
|
|
224
|
+
emptyText: 'No preferences found',
|
|
225
|
+
});
|
|
226
|
+
```
|
|
227
|
+
|
|
228
|
+
### createMemorySystemPrompt
|
|
229
|
+
|
|
230
|
+
Creates a system prompt augmented with relevant memories:
|
|
231
|
+
|
|
232
|
+
```typescript
|
|
233
|
+
import { createMemorySystemPrompt } from '@positronic/mem0';
|
|
234
|
+
|
|
235
|
+
export default brain('my-brain')
|
|
236
|
+
.brain('Chat', async ({ memory }) => {
|
|
237
|
+
const system = await createMemorySystemPrompt(
|
|
238
|
+
memory,
|
|
239
|
+
'You are a helpful assistant.',
|
|
240
|
+
'user context and preferences',
|
|
241
|
+
{
|
|
242
|
+
limit: 10,
|
|
243
|
+
memoriesHeader: '\n\nUser context:',
|
|
244
|
+
}
|
|
245
|
+
);
|
|
246
|
+
|
|
247
|
+
return { system, prompt: userMessage, tools: { /* ... */ } };
|
|
248
|
+
});
|
|
249
|
+
```
|
|
250
|
+
|
|
251
|
+
### getMemoryContext
|
|
252
|
+
|
|
253
|
+
Gets just the memory context block for manual prompt construction:
|
|
254
|
+
|
|
255
|
+
```typescript
|
|
256
|
+
import { getMemoryContext } from '@positronic/mem0';
|
|
257
|
+
|
|
258
|
+
const context = await getMemoryContext(memory, 'user preferences', {
|
|
259
|
+
limit: 5,
|
|
260
|
+
});
|
|
261
|
+
|
|
262
|
+
const system = 'You are helpful.\n\n' + (context ? '## User Context\n' + context : '');
|
|
263
|
+
```
|
|
264
|
+
|
|
265
|
+
## Memory Scoping
|
|
266
|
+
|
|
267
|
+
Memories are scoped by two identifiers:
|
|
268
|
+
|
|
269
|
+
### agentId
|
|
270
|
+
|
|
271
|
+
Automatically set to the brain/step title. Memories are isolated per agent:
|
|
272
|
+
|
|
273
|
+
```typescript
|
|
274
|
+
brain('support-agent').withMemory(memory) // agentId = 'support-agent'
|
|
275
|
+
brain('sales-agent').withMemory(memory) // agentId = 'sales-agent'
|
|
276
|
+
```
|
|
277
|
+
|
|
278
|
+
### userId
|
|
279
|
+
|
|
280
|
+
Automatically set from `currentUser.id` when the brain runs. All memory operations are automatically scoped to the current user — no need to pass userId manually:
|
|
281
|
+
|
|
282
|
+
```typescript
|
|
283
|
+
// userId is auto-bound from currentUser — just use memory directly
|
|
284
|
+
await memory.search('preferences');
|
|
285
|
+
await memory.add(messages);
|
|
286
|
+
|
|
287
|
+
// In tools — the agent just passes the fact/query, userId is automatic
|
|
288
|
+
rememberFact({ fact: 'Prefers dark mode' })
|
|
289
|
+
recallMemories({ query: 'preferences' })
|
|
290
|
+
```
|
|
291
|
+
|
|
292
|
+
See the [currentUser section in positronic-guide.md](positronic-guide.md#currentuser) for how to set the current user when running brains.
|
|
@@ -232,9 +232,60 @@ const api = {
|
|
|
232
232
|
};
|
|
233
233
|
```
|
|
234
234
|
|
|
235
|
+
## currentUser
|
|
236
|
+
|
|
237
|
+
Every brain run requires a `currentUser` — an object with at least an `id` field that identifies who is running the brain. This identity is used to scope per-user data like memory and store fields.
|
|
238
|
+
|
|
239
|
+
### How currentUser Gets Set
|
|
240
|
+
|
|
241
|
+
The way `currentUser` is provided depends on how the brain is running:
|
|
242
|
+
|
|
243
|
+
**Deployed (Cloudflare / production)**: The backend sets `currentUser` from the authenticated request. When a user hits an API endpoint to start a brain run, the auth middleware determines their identity and passes it through. You don't need to set it manually.
|
|
244
|
+
|
|
245
|
+
**Local development with `px brain run`**: The CLI passes a default user identity automatically. You don't need to do anything special.
|
|
246
|
+
|
|
247
|
+
**Local development with `runner.ts`**: When calling `runner.run()` directly, you must pass `currentUser`:
|
|
248
|
+
|
|
249
|
+
```typescript
|
|
250
|
+
import { runner } from './runner.js';
|
|
251
|
+
import myBrain from './brains/my-brain.js';
|
|
252
|
+
|
|
253
|
+
await runner.run(myBrain, {
|
|
254
|
+
currentUser: { id: 'local-dev-user' },
|
|
255
|
+
});
|
|
256
|
+
```
|
|
257
|
+
|
|
258
|
+
**In tests**: Pass `currentUser` when running the brain:
|
|
259
|
+
|
|
260
|
+
```typescript
|
|
261
|
+
const events = await collectEvents(
|
|
262
|
+
testBrain.run({
|
|
263
|
+
client: mockClient,
|
|
264
|
+
currentUser: { id: 'test-user' },
|
|
265
|
+
})
|
|
266
|
+
);
|
|
267
|
+
```
|
|
268
|
+
|
|
269
|
+
### What currentUser Scopes
|
|
270
|
+
|
|
271
|
+
- **Memory**: All memory operations (search, add) are automatically scoped to the current user. No need to pass `userId` manually — see [docs/memory-guide.md](memory-guide.md).
|
|
272
|
+
- **Store (per-user fields)**: Store fields marked with `perUser: true` are automatically scoped to the current user — see [docs/brain-dsl-guide.md](brain-dsl-guide.md).
|
|
273
|
+
|
|
274
|
+
### Accessing currentUser in Steps
|
|
275
|
+
|
|
276
|
+
`currentUser` is available in step context if you need it:
|
|
277
|
+
|
|
278
|
+
```typescript
|
|
279
|
+
export default brain('greet')
|
|
280
|
+
.step('Hello', ({ currentUser }) => ({
|
|
281
|
+
greeting: 'Hello, user ' + currentUser.id,
|
|
282
|
+
}));
|
|
283
|
+
```
|
|
284
|
+
|
|
235
285
|
## Getting Help
|
|
236
286
|
|
|
237
287
|
- **Documentation**: https://positronic.dev
|
|
238
288
|
- **CLI Help**: `px --help`
|
|
239
289
|
- **Brain DSL Guide**: `/docs/brain-dsl-guide.md` (includes UI steps for generating forms)
|
|
290
|
+
- **Memory Guide**: `/docs/memory-guide.md`
|
|
240
291
|
- **Testing Guide**: `/docs/brain-testing-guide.md`
|