@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 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.68';
57
- let cloudflareVersion = '^0.0.68';
58
- let clientVercelVersion = '^0.0.68';
59
- let genUIComponentsVersion = '^0.0.68';
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@positronic/template-new-project",
3
- "version": "0.0.68",
3
+ "version": "0.0.70",
4
4
  "publishConfig": {
5
5
  "access": "public"
6
6
  },
@@ -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
- * See docs/memory-guide.md for more details on memory configuration.
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 components.
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`