@electric-ax/agents 0.1.5 → 0.2.2

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.
Files changed (57) hide show
  1. package/dist/entrypoint.js +653 -408
  2. package/dist/index.cjs +671 -419
  3. package/dist/index.d.cts +36 -3
  4. package/dist/index.d.ts +36 -3
  5. package/dist/index.js +656 -411
  6. package/docs/entities/agents/horton.md +89 -0
  7. package/docs/entities/agents/worker.md +102 -0
  8. package/docs/entities/patterns/blackboard.md +111 -0
  9. package/docs/entities/patterns/dispatcher.md +77 -0
  10. package/docs/entities/patterns/manager-worker.md +127 -0
  11. package/docs/entities/patterns/map-reduce.md +81 -0
  12. package/docs/entities/patterns/pipeline.md +101 -0
  13. package/docs/entities/patterns/reactive-observers.md +125 -0
  14. package/docs/examples/mega-draw.md +106 -0
  15. package/docs/examples/playground.md +46 -0
  16. package/docs/index.md +208 -0
  17. package/docs/quickstart.md +201 -0
  18. package/docs/reference/agent-config.md +82 -0
  19. package/docs/reference/agent-tool.md +58 -0
  20. package/docs/reference/built-in-collections.md +334 -0
  21. package/docs/reference/cli.md +238 -0
  22. package/docs/reference/entity-definition.md +57 -0
  23. package/docs/reference/entity-handle.md +63 -0
  24. package/docs/reference/entity-registry.md +73 -0
  25. package/docs/reference/handler-context.md +108 -0
  26. package/docs/reference/runtime-handler.md +136 -0
  27. package/docs/reference/shared-state-handle.md +74 -0
  28. package/docs/reference/state-collection-proxy.md +41 -0
  29. package/docs/reference/wake-event.md +132 -0
  30. package/docs/usage/app-setup.md +165 -0
  31. package/docs/usage/clients-and-react.md +191 -0
  32. package/docs/usage/configuring-the-agent.md +136 -0
  33. package/docs/usage/context-composition.md +204 -0
  34. package/docs/usage/defining-entities.md +181 -0
  35. package/docs/usage/defining-tools.md +229 -0
  36. package/docs/usage/embedded-builtins.md +180 -0
  37. package/docs/usage/managing-state.md +93 -0
  38. package/docs/usage/overview.md +284 -0
  39. package/docs/usage/programmatic-runtime-client.md +216 -0
  40. package/docs/usage/shared-state.md +169 -0
  41. package/docs/usage/spawning-and-coordinating.md +165 -0
  42. package/docs/usage/testing.md +76 -0
  43. package/docs/usage/waking-entities.md +148 -0
  44. package/docs/usage/writing-handlers.md +267 -0
  45. package/package.json +6 -9
  46. package/skills/init.md +71 -0
  47. package/skills/quickstart/scaffold/package.json +30 -0
  48. package/skills/{tutorial → quickstart}/scaffold/tsconfig.json +8 -3
  49. package/skills/quickstart/scaffold/vite.config.ts +21 -0
  50. package/skills/quickstart/scaffold-ui/index.html +12 -0
  51. package/skills/quickstart/scaffold-ui/main.tsx +235 -0
  52. package/skills/quickstart.md +582 -0
  53. package/skills/tutorial/scaffold/package.json +0 -17
  54. package/skills/tutorial.md +0 -282
  55. /package/skills/{tutorial → quickstart}/scaffold/entities/.gitkeep +0 -0
  56. /package/skills/{tutorial → quickstart}/scaffold/lib/electric-tools.ts +0 -0
  57. /package/skills/{tutorial → quickstart}/scaffold/server.ts +0 -0
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@electric-ax/agents",
3
- "version": "0.1.5",
3
+ "version": "0.2.2",
4
4
  "description": "Built-in Electric Agents runtimes such as Horton and worker",
5
5
  "repository": {
6
6
  "type": "git",
@@ -29,26 +29,22 @@
29
29
  },
30
30
  "dependencies": {
31
31
  "@anthropic-ai/sdk": "^0.78.0",
32
- "@durable-streams/state": "npm:@electric-ax/durable-streams-state-beta@^0.3.0",
32
+ "@durable-streams/state": "npm:@electric-ax/durable-streams-state-beta@^0.3.1",
33
33
  "@mariozechner/pi-agent-core": "^0.70.2",
34
34
  "@mariozechner/pi-ai": "^0.70.2",
35
- "@mozilla/readability": "^0.6.0",
36
35
  "@sinclair/typebox": "^0.34.48",
36
+ "agent-session-protocol": "^0.0.2",
37
37
  "better-sqlite3": "^11.10.0",
38
- "jsdom": "^28.1.0",
39
38
  "nanoid": "^3.3.11",
40
39
  "pino": "^10.3.1",
41
40
  "pino-pretty": "^13.0.0",
42
41
  "sqlite-vec": "^0.1.9",
43
- "turndown": "^7.2.2",
44
- "turndown-plugin-gfm": "^1.0.2",
45
- "@electric-ax/agents-runtime": "0.0.4"
42
+ "zod": "^4.3.6",
43
+ "@electric-ax/agents-runtime": "0.1.1"
46
44
  },
47
45
  "devDependencies": {
48
46
  "@types/better-sqlite3": "^7.6.13",
49
- "@types/jsdom": "^27.0.0",
50
47
  "@types/node": "^22.19.15",
51
- "@types/turndown": "^5.0.6",
52
48
  "@vitest/coverage-v8": "^4.1.0",
53
49
  "tsdown": "^0.9.0",
54
50
  "typescript": "^5.0.0",
@@ -56,6 +52,7 @@
56
52
  },
57
53
  "files": [
58
54
  "dist",
55
+ "docs",
59
56
  "skills"
60
57
  ],
61
58
  "sideEffects": false,
package/skills/init.md ADDED
@@ -0,0 +1,71 @@
1
+ ---
2
+ description: Scaffold a new Electric Agents app project and get oriented in the codebase
3
+ whenToUse: User wants to create a new app, start a project, or scaffold an Electric Agents application
4
+ keywords:
5
+ - init
6
+ - scaffold
7
+ - new project
8
+ - create app
9
+ - starter
10
+ - setup
11
+ user-invocable: true
12
+ argument-hint: '[project-name]'
13
+ arguments:
14
+ - project_name
15
+ max: 15000
16
+ ---
17
+
18
+ # Init: Create a New Electric Agents App
19
+
20
+ Help the user scaffold and understand a new Electric Agents project.
21
+
22
+ ## Flow
23
+
24
+ ### 1. Assess experience
25
+
26
+ Before scaffolding, ask the user:
27
+
28
+ > "Are you familiar with Electric Agents concepts (entities, handlers, spawning workers)? If not, I can walk you through a hands-on quickstart first — or we can dive straight into setting up your project."
29
+
30
+ - If they want the quickstart → load the quickstart skill with `use_skill("quickstart")`
31
+ - If they want to dive in → continue to step 2
32
+
33
+ ### 2. Scaffold the project
34
+
35
+ **Ask the user where they want the project.** Suggest a sensible default (e.g., `./$project_name` relative to the working directory) but let them choose. Do not create files or directories until the user confirms the location.
36
+
37
+ If `$project_name` is not provided, ask the user what they'd like to name their project.
38
+
39
+ Run the init command to create the project:
40
+
41
+ ```
42
+ npx electric-ax agents init $project_name
43
+ ```
44
+
45
+ After the command completes, read the generated project structure to orient yourself.
46
+
47
+ ### 3. Orient the user
48
+
49
+ Walk through what was created. Read the key files and explain:
50
+
51
+ - **Project structure** — what each directory and file is for
52
+ - **Entity definitions** — where entity types are defined (e.g., `src/server/` or `entities/`)
53
+ - **Server setup** — how the HTTP server and webhook handler work
54
+ - **Frontend** — how the UI connects to the agent backend (if applicable)
55
+ - **Running it** — the commands to start the dev server
56
+
57
+ Keep explanations concise. The user can ask follow-up questions.
58
+
59
+ ### 4. Customize
60
+
61
+ Ask the user what they want to build:
62
+
63
+ > "What kind of app are you thinking of building? I can help you customize the starter — rename entity types, add new ones, adjust the tools, or modify the UI."
64
+
65
+ Help them make their first changes to the scaffolded project.
66
+
67
+ ## Rules
68
+
69
+ - Always read generated files before explaining them — don't assume the scaffold output.
70
+ - If the init command doesn't exist or fails, fall back to manual scaffolding: create the project directory, set up package.json, install dependencies, and create a basic server.ts using the pattern from the quickstart skill's scaffold directory.
71
+ - Don't overwhelm with information. Give a high-level overview first, then go deeper when asked.
@@ -0,0 +1,30 @@
1
+ {
2
+ "name": "my-electric-agents-app",
3
+ "private": true,
4
+ "type": "module",
5
+ "scripts": {
6
+ "start": "tsx server.ts",
7
+ "dev": "tsx --watch server.ts",
8
+ "dev:server": "tsx --watch server.ts",
9
+ "dev:ui": "vite",
10
+ "dev:all": "npm run dev:server & npm run dev:ui"
11
+ },
12
+ "dependencies": {
13
+ "@electric-ax/agents-runtime": "latest",
14
+ "@radix-ui/themes": "^3.3.0",
15
+ "@sinclair/typebox": "^0.34.49",
16
+ "@tanstack/db": "^0.6.0",
17
+ "@tanstack/react-db": "^0.1.78",
18
+ "react": "^19.2.4",
19
+ "react-dom": "^19.2.4",
20
+ "streamdown": "^2.5.0"
21
+ },
22
+ "devDependencies": {
23
+ "@types/react": "^19.2.14",
24
+ "@types/react-dom": "^19.2.3",
25
+ "@vitejs/plugin-react": "^5.2.0",
26
+ "tsx": "^4.19.0",
27
+ "typescript": "^5.7.0",
28
+ "vite": "^7.2.4"
29
+ }
30
+ }
@@ -3,13 +3,18 @@
3
3
  "target": "ES2022",
4
4
  "module": "ESNext",
5
5
  "moduleResolution": "bundler",
6
+ "jsx": "react-jsx",
6
7
  "strict": true,
7
8
  "esModuleInterop": true,
8
9
  "skipLibCheck": true,
10
+ "forceConsistentCasingInFileNames": true,
9
11
  "resolveJsonModule": true,
10
- "allowImportingTsExtensions": false,
11
- "noEmit": true
12
+ "isolatedModules": true,
13
+ "noEmit": true,
14
+ "paths": {
15
+ "@tanstack/db": ["./node_modules/@tanstack/db"]
16
+ }
12
17
  },
13
- "include": ["**/*.ts"],
18
+ "include": ["**/*.ts", "**/*.tsx"],
14
19
  "exclude": ["node_modules"]
15
20
  }
@@ -0,0 +1,21 @@
1
+ import path from 'node:path'
2
+ import { defineConfig } from 'vite'
3
+ import react from '@vitejs/plugin-react'
4
+
5
+ export default defineConfig({
6
+ root: `ui`,
7
+ plugins: [react()],
8
+ resolve: {
9
+ alias: {
10
+ '@tanstack/db': path.resolve(
11
+ import.meta.dirname,
12
+ `node_modules/@tanstack/db`
13
+ ),
14
+ },
15
+ },
16
+ server: {
17
+ port: 5175,
18
+ proxy: { '/api': `http://localhost:3000` },
19
+ },
20
+ build: { outDir: `../dist`, emptyOutDir: true },
21
+ })
@@ -0,0 +1,12 @@
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8" />
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0" />
6
+ <title>Perspectives Analyzer</title>
7
+ </head>
8
+ <body>
9
+ <div id="root"></div>
10
+ <script type="module" src="./main.tsx"></script>
11
+ </body>
12
+ </html>
@@ -0,0 +1,235 @@
1
+ import { useState, useEffect, useRef } from 'react'
2
+ import { createRoot } from 'react-dom/client'
3
+ import { createAgentsClient, entity } from '@electric-ax/agents-runtime'
4
+ import { useChat } from '@electric-ax/agents-runtime/react'
5
+ import {
6
+ Theme,
7
+ Box,
8
+ Flex,
9
+ Heading,
10
+ Text,
11
+ TextField,
12
+ Button,
13
+ Card,
14
+ } from '@radix-ui/themes'
15
+ import { Streamdown } from 'streamdown'
16
+ import '@radix-ui/themes/styles.css'
17
+ import 'streamdown/styles.css'
18
+ import type { EntityStreamDB } from '@electric-ax/agents-runtime'
19
+
20
+ const AGENTS_URL = `http://localhost:4437`
21
+
22
+ const AGENT_COLORS: Record<string, { bg: string; border: string }> = {
23
+ analyser: { bg: `var(--blue-3)`, border: `var(--blue-6)` },
24
+ optimist: { bg: `var(--green-3)`, border: `var(--green-6)` },
25
+ critic: { bg: `var(--red-3)`, border: `var(--red-6)` },
26
+ }
27
+
28
+ function useEntityDb(url: string | null, retryMs = 0) {
29
+ const [db, setDb] = useState<EntityStreamDB | null>(null)
30
+
31
+ useEffect(() => {
32
+ if (!url) {
33
+ setDb(null)
34
+ return
35
+ }
36
+ let cancelled = false
37
+ let observedDb: EntityStreamDB | null = null
38
+ let timer: ReturnType<typeof setTimeout> | null = null
39
+
40
+ const connect = () => {
41
+ const client = createAgentsClient({ baseUrl: AGENTS_URL })
42
+ client.observe(entity(url)).then(
43
+ (observed) => {
44
+ observedDb = observed as EntityStreamDB
45
+ if (cancelled) {
46
+ observedDb.close()
47
+ return
48
+ }
49
+ setDb(observedDb)
50
+ },
51
+ () => {
52
+ if (!cancelled && retryMs > 0) {
53
+ timer = setTimeout(connect, retryMs)
54
+ }
55
+ }
56
+ )
57
+ }
58
+ connect()
59
+
60
+ return () => {
61
+ cancelled = true
62
+ if (timer) clearTimeout(timer)
63
+ observedDb?.close()
64
+ }
65
+ }, [url, retryMs])
66
+
67
+ return db
68
+ }
69
+
70
+ interface AgentMessage {
71
+ agent: string
72
+ text: string
73
+ isStreaming: boolean
74
+ }
75
+
76
+ function useAgentMessages(
77
+ url: string | null,
78
+ agent: string,
79
+ retryMs = 0
80
+ ): AgentMessage[] {
81
+ const db = useEntityDb(url, retryMs)
82
+ const chat = useChat(db)
83
+
84
+ return chat.runs.flatMap((r, ri) =>
85
+ r.texts
86
+ .filter((t) => t.text.trim().length > 0)
87
+ .map((t, ti) => ({
88
+ agent,
89
+ text: t.text,
90
+ isStreaming:
91
+ chat.state === `working` &&
92
+ ri === chat.runs.length - 1 &&
93
+ ti === r.texts.length - 1,
94
+ }))
95
+ )
96
+ }
97
+
98
+ function MessageBubble({ msg }: { msg: AgentMessage }) {
99
+ const colors = AGENT_COLORS[msg.agent] ?? {
100
+ bg: `var(--gray-3)`,
101
+ border: `var(--gray-6)`,
102
+ }
103
+
104
+ return (
105
+ <Card
106
+ size="1"
107
+ style={{
108
+ background: colors.bg,
109
+ borderLeft: `3px solid ${colors.border}`,
110
+ }}
111
+ >
112
+ <Text size="1" weight="bold" style={{ textTransform: `capitalize` }}>
113
+ {msg.agent}
114
+ </Text>
115
+ <Box mt="1" style={{ fontSize: `var(--font-size-2)` }}>
116
+ <Streamdown isAnimating={msg.isStreaming} controls={false}>
117
+ {msg.text}
118
+ </Streamdown>
119
+ </Box>
120
+ </Card>
121
+ )
122
+ }
123
+
124
+ function App() {
125
+ const [question, setQuestion] = useState(``)
126
+ const [urls, setUrls] = useState<{
127
+ entityUrl: string
128
+ optimistUrl: string
129
+ criticUrl: string
130
+ } | null>(null)
131
+ const [loading, setLoading] = useState(false)
132
+ const bottomRef = useRef<HTMLDivElement>(null)
133
+
134
+ const analyserMessages = useAgentMessages(urls?.entityUrl ?? null, `analyser`)
135
+ const optimistMessages = useAgentMessages(
136
+ urls?.optimistUrl ?? null,
137
+ `optimist`,
138
+ 2000
139
+ )
140
+ const criticMessages = useAgentMessages(
141
+ urls?.criticUrl ?? null,
142
+ `critic`,
143
+ 2000
144
+ )
145
+
146
+ const allMessages = [
147
+ ...analyserMessages,
148
+ ...optimistMessages,
149
+ ...criticMessages,
150
+ ]
151
+
152
+ useEffect(() => {
153
+ const el = bottomRef.current?.parentElement
154
+ if (!el) return
155
+ const observer = new MutationObserver(() => {
156
+ window.scrollTo({ top: document.body.scrollHeight, behavior: `smooth` })
157
+ })
158
+ observer.observe(el, {
159
+ childList: true,
160
+ subtree: true,
161
+ characterData: true,
162
+ })
163
+ return () => observer.disconnect()
164
+ }, [urls])
165
+
166
+ const handleAnalyze = async () => {
167
+ if (!question.trim()) return
168
+ setLoading(true)
169
+ try {
170
+ const res = await fetch(`/api/analyze`, {
171
+ method: `POST`,
172
+ headers: { 'Content-Type': `application/json` },
173
+ body: JSON.stringify({ question }),
174
+ })
175
+ const data = (await res.json()) as {
176
+ entityUrl: string
177
+ optimistUrl: string
178
+ criticUrl: string
179
+ }
180
+ setUrls(data)
181
+ } catch (err) {
182
+ console.error(`Analyze failed:`, err)
183
+ } finally {
184
+ setLoading(false)
185
+ }
186
+ }
187
+
188
+ return (
189
+ <Theme appearance="light" accentColor="blue" radius="medium">
190
+ <style>{`@keyframes blink { 50% { opacity: 0; } }`}</style>
191
+ <Box maxWidth="700px" mx="auto" p="5">
192
+ <Heading size="6" mb="1">
193
+ Perspectives Analyzer
194
+ </Heading>
195
+ <Text size="2" color="gray" mb="5" as="p">
196
+ Ask a question and get two perspectives — an optimist and a critic —
197
+ then a balanced analysis.
198
+ </Text>
199
+
200
+ <Flex gap="2" mb="5">
201
+ <Box flexGrow="1">
202
+ <TextField.Root
203
+ size="3"
204
+ placeholder="Enter a question to analyze..."
205
+ value={question}
206
+ onChange={(e) => setQuestion(e.target.value)}
207
+ onKeyDown={(e) => e.key === `Enter` && handleAnalyze()}
208
+ />
209
+ </Box>
210
+ <Button
211
+ size="3"
212
+ onClick={handleAnalyze}
213
+ disabled={loading || !question.trim()}
214
+ >
215
+ {loading ? `Analyzing...` : `Analyze`}
216
+ </Button>
217
+ </Flex>
218
+
219
+ <Flex direction="column" gap="3">
220
+ {urls && allMessages.length === 0 && (
221
+ <Text color="gray" size="2">
222
+ Waiting for agents to respond...
223
+ </Text>
224
+ )}
225
+ {allMessages.map((msg, i) => (
226
+ <MessageBubble key={`${msg.agent}-${i}`} msg={msg} />
227
+ ))}
228
+ <div ref={bottomRef} />
229
+ </Flex>
230
+ </Box>
231
+ </Theme>
232
+ )
233
+ }
234
+
235
+ createRoot(document.getElementById(`root`)!).render(<App />)