@electric-ax/agents 0.1.5 → 0.2.1

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.
@@ -1,282 +0,0 @@
1
- ---
2
- description: Interactive tutorial — build a perspectives analyzer entity with the manager-worker pattern
3
- whenToUse: User asks about building entities, wants a tutorial, is new to Electric Agents, or wants to learn multi-agent patterns
4
- keywords:
5
- - tutorial
6
- - getting started
7
- - learn
8
- - multi-agent
9
- - manager-worker
10
- - perspectives
11
- - entity
12
- user-invocable: true
13
- max: 25000
14
- ---
15
-
16
- # Tutorial: Build a Perspectives Analyzer
17
-
18
- Build a `perspectives` entity that analyzes questions from an optimist and a critic using the manager-worker pattern. Use the exact code below — do not invent different code.
19
-
20
- ## Core Concepts
21
-
22
- ### What is Electric Agents?
23
-
24
- Electric Agents is a runtime for spawning and orchestrating collaborative AI agents on serverless compute.
25
-
26
- The core idea: agent sessions and communication are backed by **durable streams**. Each agent is an **entity** with its own stream of events. All agent activity — runs, tool calls, text output — is persisted to this stream. This means agents can scale to zero, survive restarts, and maintain full session history.
27
-
28
- **Why this matters for multi-agent systems**: Because everything is durable and observable, agents can spawn children, wait for results (even across restarts), observe each other's state changes, and coordinate through structured primitives — all without worrying about losing state.
29
-
30
- ### Entities
31
-
32
- An entity is a durable, addressable unit of computation. Each entity has:
33
-
34
- - A **type** (e.g., `assistant`, `worker`, `research-team`) — defined once, instantiated many times
35
- - A **URL** (e.g., `/research-team/my-team`) — its unique address
36
- - A **handler** — the function that runs each time the entity wakes up
37
- - **State** — persistent collections that survive across wakes
38
-
39
- You define entity types with `registry.define()` and create instances by spawning them.
40
-
41
- ### Handlers and Wakes
42
-
43
- An entity's handler runs in response to **wake events**:
44
-
45
- - A message arrives in the entity's inbox
46
- - A child entity finishes its run
47
- - A cron schedule fires
48
- - A state change in an observed entity
49
-
50
- The handler is **not** a long-running process. It wakes, does its work (usually running an LLM agent loop), and goes back to sleep.
51
-
52
- ### The Agent Loop
53
-
54
- `ctx.useAgent()` configures an LLM agent and `ctx.agent.run()` starts it. The agent receives conversation history, calls tools as needed, and generates a response — all persisted to the entity's durable stream.
55
-
56
- ### Spawning Children
57
-
58
- Any entity can spawn child entities. When a child finishes (and the parent registered `wake: "runFinished"`), the parent's handler runs again. The wake event includes the child's response and the status of sibling children.
59
-
60
- ### The Worker Entity
61
-
62
- The built-in `worker` type is a generic agent substrate. You configure it at spawn time with a `systemPrompt` and `tools` array (at least one tool required).
63
-
64
- ### State Collections
65
-
66
- Entities can declare persistent state collections that survive across wakes, allowing coordination patterns like tracking which children have completed.
67
-
68
- ## Before starting
69
-
70
- Read `server.ts` in the working directory:
71
-
72
- - **Has `registerPerspectives`**: resume from where they left off (read `entities/perspectives.ts` to determine the step)
73
- - **Has `server.ts` but no perspectives**: go to Step 1
74
- - **No `server.ts`**: scaffold the project — spawn a worker (`tools: ["bash"]`, systemPrompt: `"Set up an Electric Agents app project."`, initialMessage: `"mkdir -p TARGET/lib TARGET/entities && cp SKILL_DIR/scaffold/* TARGET/ && cp SKILL_DIR/scaffold/lib/* TARGET/lib/ && cp SKILL_DIR/scaffold/.env TARGET/ && cd TARGET && pnpm install && pnpm dev &"` — replace SKILL_DIR and TARGET). Then proceed to Step 1 while the worker runs. Wait for the worker to finish before writing files.
75
-
76
- ## Steps
77
-
78
- **Step 1 — Welcome + first entity.** In one message: introduce Electric Agents using the Core Concepts above, preview the perspectives analyzer, and show the Step 1 code. Ask to write.
79
-
80
- **Step 2 — After confirmation:** write `entities/perspectives.ts` with Step 1 code. Give CLI commands. Explain spawning briefly, show Step 2 code (adds one worker). Ask to write.
81
-
82
- **Step 3 — After confirmation:** write the updated file. Give CLI commands. Explain coordination, show Step 3 code (adds critic + state). Ask to write.
83
-
84
- **Step 4 — After confirmation:** write the updated file. Give CLI commands.
85
-
86
- **Step 5 — Wire up.** Read `server.ts`, show the import change, ask to write, update it.
87
-
88
- **Step 6 — Recap.**
89
-
90
- ## Rules
91
-
92
- - Use the exact code below. Write files with your write tool.
93
- - `server.ts` is at the working directory root. Entity files go in `entities/`.
94
- - Worker spawn args MUST include `tools` array (e.g. `tools: ["bash", "read"]`).
95
- - Prefer showing what changed between steps rather than repeating the entire file.
96
- - Use `edit` tool for small changes (like updating server.ts). Use `write` for full entity file updates.
97
-
98
- ---
99
-
100
- # Code
101
-
102
- ## Step 1: Minimal entity
103
-
104
- `entities/perspectives.ts`:
105
-
106
- ```typescript
107
- import type { EntityRegistry } from '@electric-ax/agents-runtime'
108
-
109
- export function registerPerspectives(registry: EntityRegistry) {
110
- registry.define('perspectives', {
111
- description: 'Analyzes questions from multiple perspectives',
112
- async handler(ctx) {
113
- ctx.useAgent({
114
- systemPrompt:
115
- 'You are a balanced analyst. When given a question, provide a thoughtful analysis.',
116
- model: 'claude-sonnet-4-6',
117
- tools: [...ctx.electricTools],
118
- })
119
- await ctx.agent.run()
120
- },
121
- })
122
- }
123
- ```
124
-
125
- `server.ts` additions:
126
-
127
- ```typescript
128
- import { registerPerspectives } from './entities/perspectives'
129
- registerPerspectives(registry)
130
- ```
131
-
132
- Test: `pnpm electric-agents spawn /perspectives/test-1 && pnpm electric-agents send /perspectives/test-1 "Is remote work better than office work?" && pnpm electric-agents observe /perspectives/test-1`
133
-
134
- ## Step 2: One worker
135
-
136
- Full `entities/perspectives.ts`:
137
-
138
- ```typescript
139
- import type {
140
- EntityRegistry,
141
- HandlerContext,
142
- } from '@electric-ax/agents-runtime'
143
- import { Type } from '@sinclair/typebox'
144
-
145
- function createAnalyzeTool(ctx: HandlerContext) {
146
- return {
147
- name: 'analyze_question',
148
- label: 'Analyze Question',
149
- description: 'Spawns an optimist worker to analyze a question.',
150
- parameters: Type.Object({
151
- question: Type.String({ description: 'The question to analyze' }),
152
- }),
153
- execute: async (_toolCallId: string, params: unknown) => {
154
- const { question } = params as { question: string }
155
- const parentId = ctx.entityUrl.split('/').pop()
156
- await ctx.spawn(
157
- 'worker',
158
- `${parentId}-optimist`,
159
- {
160
- systemPrompt:
161
- 'You are an optimist analyst. Provide an enthusiastic, positive analysis focusing on opportunities and benefits.',
162
- tools: ['bash', 'read'],
163
- },
164
- { initialMessage: question, wake: 'runFinished' }
165
- )
166
- return {
167
- content: [
168
- {
169
- type: 'text' as const,
170
- text: "Spawned optimist worker. You'll be woken when it finishes.",
171
- },
172
- ],
173
- details: {},
174
- }
175
- },
176
- }
177
- }
178
-
179
- export function registerPerspectives(registry: EntityRegistry) {
180
- registry.define('perspectives', {
181
- description: 'Analyzes questions from multiple perspectives',
182
- async handler(ctx) {
183
- ctx.useAgent({
184
- systemPrompt: `You are a balanced analyst.\n\nWhen given a question:\n1. Call analyze_question with the question.\n2. End your turn. You'll be woken when the worker finishes.\n3. When woken, finished_child.response contains the analysis.\n4. Present it to the user.`,
185
- model: 'claude-sonnet-4-6',
186
- tools: [...ctx.electricTools, createAnalyzeTool(ctx)],
187
- })
188
- await ctx.agent.run()
189
- },
190
- })
191
- }
192
- ```
193
-
194
- Test: `pnpm electric-agents spawn /perspectives/test-2 && pnpm electric-agents send /perspectives/test-2 "Is remote work better than office work?" && pnpm electric-agents observe /perspectives/test-2`
195
-
196
- ## Step 3: Two workers + state
197
-
198
- Full `entities/perspectives.ts`:
199
-
200
- ```typescript
201
- import type {
202
- EntityRegistry,
203
- HandlerContext,
204
- } from '@electric-ax/agents-runtime'
205
- import { Type } from '@sinclair/typebox'
206
-
207
- const PERSPECTIVES = [
208
- {
209
- id: 'optimist',
210
- systemPrompt:
211
- 'You are an optimist analyst. Provide an enthusiastic, positive analysis focusing on opportunities and benefits.',
212
- },
213
- {
214
- id: 'critic',
215
- systemPrompt:
216
- 'You are a critical analyst. Provide a sharp analysis focusing on risks, downsides, and challenges.',
217
- },
218
- ]
219
-
220
- function createAnalyzeTool(ctx: HandlerContext) {
221
- return {
222
- name: 'analyze_question',
223
- label: 'Analyze Question',
224
- description: 'Spawns optimist and critic workers to analyze a question.',
225
- parameters: Type.Object({
226
- question: Type.String({ description: 'The question to analyze' }),
227
- }),
228
- execute: async (_toolCallId: string, params: unknown) => {
229
- const { question } = params as { question: string }
230
- const parentId = ctx.entityUrl.split('/').pop()
231
- for (const p of PERSPECTIVES) {
232
- const childId = `${parentId}-${p.id}`
233
- await ctx.spawn(
234
- 'worker',
235
- childId,
236
- { systemPrompt: p.systemPrompt, tools: ['bash', 'read'] },
237
- { initialMessage: question, wake: 'runFinished' }
238
- )
239
- ctx.db.actions.children_insert({
240
- row: { key: p.id, url: `/worker/${childId}` },
241
- })
242
- }
243
- return {
244
- content: [
245
- {
246
- type: 'text' as const,
247
- text: 'Spawned optimist and critic workers.',
248
- },
249
- ],
250
- details: {},
251
- }
252
- },
253
- }
254
- }
255
-
256
- export function registerPerspectives(registry: EntityRegistry) {
257
- registry.define('perspectives', {
258
- description:
259
- 'Analyzes questions from two perspectives: optimist and critic',
260
- state: { children: { primaryKey: 'key' } },
261
- async handler(ctx) {
262
- ctx.useAgent({
263
- systemPrompt: `You are a balanced analyst.\n\n1. Call analyze_question with the question.\n2. End your turn. You'll be woken as each worker finishes.\n3. Each wake includes finished_child.response and other_children.\n4. Once both are done, synthesize a balanced response.`,
264
- model: 'claude-sonnet-4-6',
265
- tools: [...ctx.electricTools, createAnalyzeTool(ctx)],
266
- })
267
- await ctx.agent.run()
268
- },
269
- })
270
- }
271
- ```
272
-
273
- Test: `pnpm electric-agents spawn /perspectives/test-3 && pnpm electric-agents send /perspectives/test-3 "Is remote work better than office work?" && pnpm electric-agents observe /perspectives/test-3`
274
-
275
- ## What you learned
276
-
277
- - `registry.define()` — entity types with description, state, handler
278
- - `ctx.useAgent()` + `ctx.agent.run()` — configure and run an LLM agent
279
- - `ctx.spawn()` — spawn child entities with custom prompts
280
- - Wake events — parents wake when children finish
281
- - State collections — track data across wakes
282
- - The worker pattern — one generic type, many roles