@electric-ax/agents 0.4.6 → 0.4.9

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.
@@ -4,7 +4,7 @@ import fs from "node:fs";
4
4
  import pino from "pino";
5
5
  import { fileURLToPath } from "node:url";
6
6
  import { appendPathToUrl, completeWithLowCostModel, createEntityRegistry, createPullWakeRunner, createRuntimeHandler, createSkillTools, createSkillsRegistry, db, detectAvailableProviders, readCodexAccessToken, registerToolProvider, unregisterToolProvider } from "@electric-ax/agents-runtime";
7
- import { eq, not, queryOnce } from "@durable-streams/state";
7
+ import { braveSearchTool, createBashTool, createEditTool, createEventSourceTools, createFetchUrlTool, createReadFileTool, createSendTool, createWriteTool, fetchUrlTool } from "@electric-ax/agents-runtime/tools";
8
8
  import { z } from "zod";
9
9
  import { createHash } from "node:crypto";
10
10
  import fs$1 from "node:fs/promises";
@@ -13,7 +13,6 @@ import { Type } from "@sinclair/typebox";
13
13
  import { load } from "sqlite-vec";
14
14
  import { nanoid } from "nanoid";
15
15
  import { getModels } from "@mariozechner/pi-ai";
16
- import { braveSearchTool, createBashTool, createEditTool, createFetchUrlTool, createReadFileTool, createSendTool, createWriteTool, fetchUrlTool } from "@electric-ax/agents-runtime/tools";
17
16
  import { bridgeMcpTool, buildPromptTools, buildResourceTools, createRegistry, keychainPersistence, loadConfig, mcp, watchConfig } from "@electric-ax/agents-mcp";
18
17
 
19
18
  //#region src/log.ts
@@ -652,9 +651,9 @@ function createHortonDocsSupport(workingDirectory, opts = {}) {
652
651
  },
653
652
  createSearchTool() {
654
653
  return {
655
- name: `search_durable_agents_docs`,
656
- label: `Search Durable Agents Docs`,
657
- description: `Run a hybrid BM25 plus vector search over the local Durable Agents documentation index.`,
654
+ name: `search_electric_agents_docs`,
655
+ label: `Search Electric Agents Docs`,
656
+ description: `Run a hybrid BM25 plus vector search over the local Electric Agents documentation index.`,
658
657
  parameters: Type.Object({
659
658
  query: Type.String({ description: `The docs question or search query to run.` }),
660
659
  limit: Type.Optional(Type.Number({
@@ -785,12 +784,14 @@ const REASONING_EFFORT_VALUES = [
785
784
  const DEFAULT_ANTHROPIC_MODEL = `claude-sonnet-4-6`;
786
785
  const DEFAULT_OPENAI_MODEL = `gpt-4.1`;
787
786
  const DEFAULT_CODEX_MODEL = `gpt-5.4`;
787
+ const DEFAULT_DEEPSEEK_MODEL = `deepseek-v4-flash`;
788
788
  function modelValue(provider, id) {
789
789
  return `${provider}:${id}`;
790
790
  }
791
791
  function providerLabel(provider) {
792
792
  if (provider === `anthropic`) return `Anthropic`;
793
793
  if (provider === `openai-codex`) return `OpenAI Codex`;
794
+ if (provider === `deepseek`) return `DeepSeek`;
794
795
  return `OpenAI`;
795
796
  }
796
797
  function configuredProviders() {
@@ -817,6 +818,9 @@ async function fetchAvailableModelIds(provider) {
817
818
  "anthropic-version": `2023-06-01`
818
819
  },
819
820
  signal: AbortSignal.timeout(3e3)
821
+ }) : provider === `deepseek` ? await fetch(`https://api.deepseek.com/v1/models`, {
822
+ headers: { authorization: `Bearer ${process.env.DEEPSEEK_API_KEY ?? ``}` },
823
+ signal: AbortSignal.timeout(3e3)
820
824
  }) : await fetch(`https://api.openai.com/v1/models`, {
821
825
  headers: { authorization: `Bearer ${process.env.OPENAI_API_KEY ?? ``}` },
822
826
  signal: AbortSignal.timeout(3e3)
@@ -877,7 +881,7 @@ async function createBuiltinModelCatalog(options = {}) {
877
881
  if (providers.length === 0 && options.allowMockFallback) return mockFallbackCatalog();
878
882
  const choices = (await Promise.all(providers.map((provider) => choicesForProvider(provider)))).flat();
879
883
  if (choices.length === 0) return options.allowMockFallback ? mockFallbackCatalog() : null;
880
- const defaultChoice = choices.find((choice) => choice.provider === `anthropic` && choice.id === DEFAULT_ANTHROPIC_MODEL) ?? choices.find((choice) => choice.provider === `openai` && choice.id === DEFAULT_OPENAI_MODEL) ?? choices.find((choice) => choice.provider === `openai-codex` && choice.id === DEFAULT_CODEX_MODEL) ?? choices[0];
884
+ const defaultChoice = choices.find((choice) => choice.provider === `anthropic` && choice.id === DEFAULT_ANTHROPIC_MODEL) ?? choices.find((choice) => choice.provider === `openai` && choice.id === DEFAULT_OPENAI_MODEL) ?? choices.find((choice) => choice.provider === `openai-codex` && choice.id === DEFAULT_CODEX_MODEL) ?? choices.find((choice) => choice.provider === `deepseek` && choice.id === DEFAULT_DEEPSEEK_MODEL) ?? choices[0];
881
885
  return {
882
886
  choices,
883
887
  defaultChoice
@@ -997,9 +1001,10 @@ async function generateTitle(userMessage, llmCall, onFallback) {
997
1001
  }
998
1002
  }
999
1003
  function buildHortonSystemPrompt(workingDirectory, opts = {}) {
1000
- const docsTools = opts.hasDocsSupport ? `\n- search_durable_agents_docs: hybrid search over the built-in Durable Agents docs index` : ``;
1004
+ const docsTools = opts.hasDocsSupport ? `\n- search_electric_agents_docs: hybrid search over the built-in Electric Agents docs index` : ``;
1005
+ const eventSourceTools = opts.hasEventSourceTools ? `\n- list_event_sources: list external webhook/event feeds you can subscribe to, including available buckets and parameters\n- subscribe_event_source: subscribe yourself to one of those feeds or buckets so matching future events wake you\n- list_event_source_subscriptions: list your active event source subscriptions\n- unsubscribe_event_source: remove one of your event source subscriptions by id` : ``;
1001
1006
  const skillsTools = opts.hasSkills ? `\n- use_skill: load a skill (knowledge, instructions, or a tutorial) into your context to help with the user's request\n- remove_skill: unload a skill from context when you're done with it` : ``;
1002
- const docsGuidance = opts.hasDocsSupport ? `\n- For ANY question about Electric Agents, Durable Agents, or this framework, ALWAYS use search_durable_agents_docs FIRST. Do not use web_search or fetch_url for Electric Agents topics unless the docs search returns no useful results.\n- The search tool returns chunk content directly — you do not need to read the source files.\n- Use repo read/bash tools only for non-doc files or when you need to inspect exact implementation code in the workspace.` : ``;
1007
+ const docsGuidance = opts.hasDocsSupport ? `\n- For ANY question about Electric Agents or this framework, ALWAYS use search_electric_agents_docs FIRST. Do not use web_search or fetch_url for Electric Agents topics unless the docs search returns no useful results.\n- The search tool returns chunk content directly — you do not need to read the source files.\n- Use repo read/bash tools only for non-doc files or when you need to inspect exact implementation code in the workspace.` : ``;
1003
1008
  const skillsGuidance = opts.hasSkills ? `\n# Skills\nYou have access to skills — specialized knowledge and guided workflows you can load on demand. Your context includes a skills catalog listing what's available. When the user's request matches a skill's description or keywords, load it with use_skill.
1004
1009
 
1005
1010
  Some skills are user-invocable — the user can trigger them with a slash command like \`/quickstart\`. When you see a message starting with \`/\` followed by a skill name, load that skill immediately with use_skill. Pass any text after the skill name as args.
@@ -1018,7 +1023,7 @@ Do NOT load a skill and then ignore its instructions. The skill is there because
1018
1023
  When a user is new or asks how to get started with Electric Agents, **don't assume a single path**. Present the options and let them choose:
1019
1024
 
1020
1025
  - **Learn the concepts first** → Explain what Electric Agents is, answer questions, point to docs.
1021
- Use search_durable_agents_docs to look up answers. Only load the quickstart skill if the user explicitly asks for a hands-on guided tutorial.
1026
+ Use search_electric_agents_docs to look up answers. Only load the quickstart skill if the user explicitly asks for a hands-on guided tutorial.
1022
1027
 
1023
1028
  - **Hands-on guided tutorial** → Load the quickstart skill (or tell them to type \`/quickstart\`).
1024
1029
  This is a step-by-step build that takes them from zero to a running app.
@@ -1028,11 +1033,11 @@ When a user is new or asks how to get started with Electric Agents, **don't assu
1028
1033
  This sets up project structure and orients them in the codebase.
1029
1034
 
1030
1035
  - **Have a specific question?** → Answer it directly.
1031
- Use search_durable_agents_docs first, then fall back to fetch_url or general knowledge if needed.
1036
+ Use search_electric_agents_docs first, then fall back to fetch_url or general knowledge if needed.
1032
1037
 
1033
1038
  Don't force onboarding. If someone just wants to chat or code, let them. When in doubt, ask what they'd like to do rather than picking a path for them.`;
1034
1039
  const docsUrlGuidance = opts.docsUrl ? `\n# Electric Agents documentation
1035
- - ${opts.hasDocsSupport ? `If search_durable_agents_docs is available, use it first (faster, hybrid search).` : `Use fetch_url to look up documentation pages.`}
1040
+ - ${opts.hasDocsSupport ? `If search_electric_agents_docs is available, use it first (faster, hybrid search).` : `Use fetch_url to look up documentation pages.`}
1036
1041
  - The Electric Agents docs site is at ${opts.docsUrl}
1037
1042
  - The docs site covers: Usage (entity definition, handlers, tools, state, spawning, coordination, waking, shared state, client integration, app setup), Reference (handler context, entity definitions, configurations, tools, state proxies, wake events, registries), Entities (Horton, Worker), and Patterns (Manager-Worker, Pipeline, Map-Reduce, Dispatcher, Blackboard, Reactive Observers).
1038
1043
  - For general coding questions unrelated to Electric Agents, use web_search or your own knowledge.` : ``;
@@ -1051,8 +1056,8 @@ When a user opens with a greeting ("hi", "hello", "hey", etc.) or a broad statem
1051
1056
  - web_search: search the web
1052
1057
  - fetch_url: fetch and convert a URL to markdown
1053
1058
  - spawn_worker: dispatch a subagent for an isolated task
1054
- - send: send a message to an Electric Agent/entity by entity URL
1055
- ${docsTools}${skillsTools}
1059
+ - send: send a message to an Electric Agent/entity. To schedule future work for yourself, call send with self: true and afterMs.
1060
+ ${eventSourceTools}${docsTools}${skillsTools}
1056
1061
 
1057
1062
  # Working with files
1058
1063
  - Prefer edit over write when modifying existing files.
@@ -1085,6 +1090,11 @@ Report outcomes faithfully. If a command failed, say so with the relevant output
1085
1090
  Working directory: ${workingDirectory}
1086
1091
  The current year is ${new Date().getFullYear()}.`;
1087
1092
  }
1093
+ function getToolName(tool) {
1094
+ if (typeof tool !== `object` || tool === null) return null;
1095
+ const name = tool.name;
1096
+ return typeof name === `string` ? name : null;
1097
+ }
1088
1098
  function createHortonTools(workingDirectory, ctx, readSet, opts = {}) {
1089
1099
  return [
1090
1100
  createBashTool(workingDirectory),
@@ -1099,7 +1109,7 @@ function createHortonTools(workingDirectory, ctx, readSet, opts = {}) {
1099
1109
  logPrefix: opts.logPrefix ?? `[horton]`
1100
1110
  })] : [fetchUrlTool],
1101
1111
  createSpawnWorkerTool(ctx, opts.modelConfig),
1102
- createSendTool(ctx.send),
1112
+ createSendTool(ctx.send, { selfEntityUrl: ctx.entityUrl }),
1103
1113
  ...opts.docsSearchTool ? [opts.docsSearchTool] : []
1104
1114
  ];
1105
1115
  }
@@ -1113,11 +1123,14 @@ function payloadToTitleText(payload) {
1113
1123
  return String(payload);
1114
1124
  }
1115
1125
  async function extractFirstUserMessage(ctx) {
1116
- const firstMessage = await queryOnce((q) => q.from({ inbox: ctx.db.collections.inbox }).where(({ inbox }) => not(eq(inbox.from, `system`))).orderBy(({ inbox }) => inbox._seq, `asc`).findOne());
1126
+ const firstMessage = ctx.db.collections.inbox.toArray.filter((message) => message.from !== `system`).sort((left, right) => messageSeq(left) - messageSeq(right))[0];
1117
1127
  if (!firstMessage) return null;
1118
1128
  const text = payloadToTitleText(firstMessage.payload);
1119
1129
  return text.length > 0 ? text : null;
1120
1130
  }
1131
+ function messageSeq(message) {
1132
+ return typeof message._seq === `number` ? message._seq : -1;
1133
+ }
1121
1134
  function readAgentsMd(workingDirectory) {
1122
1135
  const agentsMdPath = path.join(workingDirectory, `AGENTS.md`);
1123
1136
  try {
@@ -1151,6 +1164,7 @@ function createAssistantHandler(options) {
1151
1164
  ...skillsRegistry && skillsRegistry.catalog.size > 0 ? createSkillTools(skillsRegistry, ctx) : [],
1152
1165
  ...mcp.tools()
1153
1166
  ];
1167
+ const hasEventSourceTools = tools.some((tool) => getToolName(tool) === `list_event_sources`);
1154
1168
  const titlePromise = ctx.firstWake && !ctx.tags.title ? (async () => {
1155
1169
  const firstUserMessage = await extractFirstUserMessage(ctx);
1156
1170
  if (!firstUserMessage) return;
@@ -1237,7 +1251,8 @@ function createAssistantHandler(options) {
1237
1251
  hasSkills,
1238
1252
  docsUrl,
1239
1253
  modelProvider: modelConfig.provider,
1240
- modelId: String(modelConfig.model)
1254
+ modelId: String(modelConfig.model),
1255
+ hasEventSourceTools
1241
1256
  }),
1242
1257
  ...modelConfig,
1243
1258
  tools,
@@ -1277,15 +1292,7 @@ function registerHorton(registry, options) {
1277
1292
  creationSchema: hortonCreationSchema,
1278
1293
  handler: assistantHandler
1279
1294
  });
1280
- const typeNames = [`horton`];
1281
- if (streamFn) {
1282
- registry.define(`chat`, {
1283
- description: `Compatibility alias for the built-in assistant type.`,
1284
- handler: assistantHandler
1285
- });
1286
- typeNames.push(`chat`);
1287
- }
1288
- return typeNames;
1295
+ return [`horton`];
1289
1296
  }
1290
1297
 
1291
1298
  //#endregion
@@ -1349,7 +1356,7 @@ function buildToolsForWorker(tools, workingDirectory, ctx, readSet) {
1349
1356
  out.push(createSpawnWorkerTool(ctx));
1350
1357
  break;
1351
1358
  case `send`:
1352
- out.push(createSendTool(ctx.send));
1359
+ out.push(createSendTool(ctx.send, { selfEntityUrl: ctx.entityUrl }));
1353
1360
  break;
1354
1361
  }
1355
1362
  return out;
@@ -1477,6 +1484,27 @@ function registerWorker(registry, options) {
1477
1484
 
1478
1485
  //#endregion
1479
1486
  //#region src/bootstrap.ts
1487
+ function toolName(tool) {
1488
+ return typeof tool.name === `string` ? tool.name : null;
1489
+ }
1490
+ function dedupeToolsByName(tools) {
1491
+ const seen = new Set();
1492
+ const deduped = [];
1493
+ for (const tool of tools) {
1494
+ const name = toolName(tool);
1495
+ if (name && seen.has(name)) continue;
1496
+ if (name) seen.add(name);
1497
+ deduped.push(tool);
1498
+ }
1499
+ return deduped;
1500
+ }
1501
+ function createBuiltinElectricTools(custom) {
1502
+ return async (context) => {
1503
+ const builtinTools = createEventSourceTools(context);
1504
+ const customTools = custom ? await custom(context) : [];
1505
+ return dedupeToolsByName([...builtinTools, ...customTools]);
1506
+ };
1507
+ }
1480
1508
  async function createBuiltinAgentHandler(options) {
1481
1509
  const { agentServerUrl, serveEndpoint, workingDirectory, streamFn, createElectricTools, publicUrl, runtimeName, baseSkillsDir: baseSkillsDirOverride, serverHeaders, defaultDispatchPolicyForType } = options;
1482
1510
  const modelCatalog = await createBuiltinModelCatalog({ allowMockFallback: Boolean(streamFn) });
@@ -1519,7 +1547,7 @@ async function createBuiltinAgentHandler(options) {
1519
1547
  defaultDispatchPolicyForType,
1520
1548
  serverHeaders,
1521
1549
  idleTimeout: 5 * 6e4,
1522
- createElectricTools,
1550
+ createElectricTools: createBuiltinElectricTools(createElectricTools),
1523
1551
  publicUrl,
1524
1552
  name: runtimeName ?? `builtin-agents`
1525
1553
  });
package/dist/index.cjs CHANGED
@@ -26,9 +26,9 @@ const require_server_headers = require('./server-headers-65vIhxvJ.cjs');
26
26
  const node_path = __toESM(require("node:path"));
27
27
  const node_url = __toESM(require("node:url"));
28
28
  const __electric_ax_agents_runtime = __toESM(require("@electric-ax/agents-runtime"));
29
+ const __electric_ax_agents_runtime_tools = __toESM(require("@electric-ax/agents-runtime/tools"));
29
30
  const node_fs = __toESM(require("node:fs"));
30
31
  const pino = __toESM(require("pino"));
31
- const __durable_streams_state = __toESM(require("@durable-streams/state"));
32
32
  const zod = __toESM(require("zod"));
33
33
  const node_crypto = __toESM(require("node:crypto"));
34
34
  const node_fs_promises = __toESM(require("node:fs/promises"));
@@ -37,7 +37,6 @@ const __sinclair_typebox = __toESM(require("@sinclair/typebox"));
37
37
  const sqlite_vec = __toESM(require("sqlite-vec"));
38
38
  const nanoid = __toESM(require("nanoid"));
39
39
  const __mariozechner_pi_ai = __toESM(require("@mariozechner/pi-ai"));
40
- const __electric_ax_agents_runtime_tools = __toESM(require("@electric-ax/agents-runtime/tools"));
41
40
  const __electric_ax_agents_mcp = __toESM(require("@electric-ax/agents-mcp"));
42
41
 
43
42
  //#region src/log.ts
@@ -676,9 +675,9 @@ function createHortonDocsSupport(workingDirectory, opts = {}) {
676
675
  },
677
676
  createSearchTool() {
678
677
  return {
679
- name: `search_durable_agents_docs`,
680
- label: `Search Durable Agents Docs`,
681
- description: `Run a hybrid BM25 plus vector search over the local Durable Agents documentation index.`,
678
+ name: `search_electric_agents_docs`,
679
+ label: `Search Electric Agents Docs`,
680
+ description: `Run a hybrid BM25 plus vector search over the local Electric Agents documentation index.`,
682
681
  parameters: __sinclair_typebox.Type.Object({
683
682
  query: __sinclair_typebox.Type.String({ description: `The docs question or search query to run.` }),
684
683
  limit: __sinclair_typebox.Type.Optional(__sinclair_typebox.Type.Number({
@@ -809,12 +808,14 @@ const REASONING_EFFORT_VALUES = [
809
808
  const DEFAULT_ANTHROPIC_MODEL = `claude-sonnet-4-6`;
810
809
  const DEFAULT_OPENAI_MODEL = `gpt-4.1`;
811
810
  const DEFAULT_CODEX_MODEL = `gpt-5.4`;
811
+ const DEFAULT_DEEPSEEK_MODEL = `deepseek-v4-flash`;
812
812
  function modelValue(provider, id) {
813
813
  return `${provider}:${id}`;
814
814
  }
815
815
  function providerLabel(provider) {
816
816
  if (provider === `anthropic`) return `Anthropic`;
817
817
  if (provider === `openai-codex`) return `OpenAI Codex`;
818
+ if (provider === `deepseek`) return `DeepSeek`;
818
819
  return `OpenAI`;
819
820
  }
820
821
  function configuredProviders() {
@@ -841,6 +842,9 @@ async function fetchAvailableModelIds(provider) {
841
842
  "anthropic-version": `2023-06-01`
842
843
  },
843
844
  signal: AbortSignal.timeout(3e3)
845
+ }) : provider === `deepseek` ? await fetch(`https://api.deepseek.com/v1/models`, {
846
+ headers: { authorization: `Bearer ${process.env.DEEPSEEK_API_KEY ?? ``}` },
847
+ signal: AbortSignal.timeout(3e3)
844
848
  }) : await fetch(`https://api.openai.com/v1/models`, {
845
849
  headers: { authorization: `Bearer ${process.env.OPENAI_API_KEY ?? ``}` },
846
850
  signal: AbortSignal.timeout(3e3)
@@ -901,7 +905,7 @@ async function createBuiltinModelCatalog(options = {}) {
901
905
  if (providers.length === 0 && options.allowMockFallback) return mockFallbackCatalog();
902
906
  const choices = (await Promise.all(providers.map((provider) => choicesForProvider(provider)))).flat();
903
907
  if (choices.length === 0) return options.allowMockFallback ? mockFallbackCatalog() : null;
904
- const defaultChoice = choices.find((choice) => choice.provider === `anthropic` && choice.id === DEFAULT_ANTHROPIC_MODEL) ?? choices.find((choice) => choice.provider === `openai` && choice.id === DEFAULT_OPENAI_MODEL) ?? choices.find((choice) => choice.provider === `openai-codex` && choice.id === DEFAULT_CODEX_MODEL) ?? choices[0];
908
+ const defaultChoice = choices.find((choice) => choice.provider === `anthropic` && choice.id === DEFAULT_ANTHROPIC_MODEL) ?? choices.find((choice) => choice.provider === `openai` && choice.id === DEFAULT_OPENAI_MODEL) ?? choices.find((choice) => choice.provider === `openai-codex` && choice.id === DEFAULT_CODEX_MODEL) ?? choices.find((choice) => choice.provider === `deepseek` && choice.id === DEFAULT_DEEPSEEK_MODEL) ?? choices[0];
905
909
  return {
906
910
  choices,
907
911
  defaultChoice
@@ -1022,9 +1026,10 @@ async function generateTitle(userMessage, llmCall, onFallback) {
1022
1026
  }
1023
1027
  }
1024
1028
  function buildHortonSystemPrompt(workingDirectory, opts = {}) {
1025
- const docsTools = opts.hasDocsSupport ? `\n- search_durable_agents_docs: hybrid search over the built-in Durable Agents docs index` : ``;
1029
+ const docsTools = opts.hasDocsSupport ? `\n- search_electric_agents_docs: hybrid search over the built-in Electric Agents docs index` : ``;
1030
+ const eventSourceTools = opts.hasEventSourceTools ? `\n- list_event_sources: list external webhook/event feeds you can subscribe to, including available buckets and parameters\n- subscribe_event_source: subscribe yourself to one of those feeds or buckets so matching future events wake you\n- list_event_source_subscriptions: list your active event source subscriptions\n- unsubscribe_event_source: remove one of your event source subscriptions by id` : ``;
1026
1031
  const skillsTools = opts.hasSkills ? `\n- use_skill: load a skill (knowledge, instructions, or a tutorial) into your context to help with the user's request\n- remove_skill: unload a skill from context when you're done with it` : ``;
1027
- const docsGuidance = opts.hasDocsSupport ? `\n- For ANY question about Electric Agents, Durable Agents, or this framework, ALWAYS use search_durable_agents_docs FIRST. Do not use web_search or fetch_url for Electric Agents topics unless the docs search returns no useful results.\n- The search tool returns chunk content directly — you do not need to read the source files.\n- Use repo read/bash tools only for non-doc files or when you need to inspect exact implementation code in the workspace.` : ``;
1032
+ const docsGuidance = opts.hasDocsSupport ? `\n- For ANY question about Electric Agents or this framework, ALWAYS use search_electric_agents_docs FIRST. Do not use web_search or fetch_url for Electric Agents topics unless the docs search returns no useful results.\n- The search tool returns chunk content directly — you do not need to read the source files.\n- Use repo read/bash tools only for non-doc files or when you need to inspect exact implementation code in the workspace.` : ``;
1028
1033
  const skillsGuidance = opts.hasSkills ? `\n# Skills\nYou have access to skills — specialized knowledge and guided workflows you can load on demand. Your context includes a skills catalog listing what's available. When the user's request matches a skill's description or keywords, load it with use_skill.
1029
1034
 
1030
1035
  Some skills are user-invocable — the user can trigger them with a slash command like \`/quickstart\`. When you see a message starting with \`/\` followed by a skill name, load that skill immediately with use_skill. Pass any text after the skill name as args.
@@ -1043,7 +1048,7 @@ Do NOT load a skill and then ignore its instructions. The skill is there because
1043
1048
  When a user is new or asks how to get started with Electric Agents, **don't assume a single path**. Present the options and let them choose:
1044
1049
 
1045
1050
  - **Learn the concepts first** → Explain what Electric Agents is, answer questions, point to docs.
1046
- Use search_durable_agents_docs to look up answers. Only load the quickstart skill if the user explicitly asks for a hands-on guided tutorial.
1051
+ Use search_electric_agents_docs to look up answers. Only load the quickstart skill if the user explicitly asks for a hands-on guided tutorial.
1047
1052
 
1048
1053
  - **Hands-on guided tutorial** → Load the quickstart skill (or tell them to type \`/quickstart\`).
1049
1054
  This is a step-by-step build that takes them from zero to a running app.
@@ -1053,11 +1058,11 @@ When a user is new or asks how to get started with Electric Agents, **don't assu
1053
1058
  This sets up project structure and orients them in the codebase.
1054
1059
 
1055
1060
  - **Have a specific question?** → Answer it directly.
1056
- Use search_durable_agents_docs first, then fall back to fetch_url or general knowledge if needed.
1061
+ Use search_electric_agents_docs first, then fall back to fetch_url or general knowledge if needed.
1057
1062
 
1058
1063
  Don't force onboarding. If someone just wants to chat or code, let them. When in doubt, ask what they'd like to do rather than picking a path for them.`;
1059
1064
  const docsUrlGuidance = opts.docsUrl ? `\n# Electric Agents documentation
1060
- - ${opts.hasDocsSupport ? `If search_durable_agents_docs is available, use it first (faster, hybrid search).` : `Use fetch_url to look up documentation pages.`}
1065
+ - ${opts.hasDocsSupport ? `If search_electric_agents_docs is available, use it first (faster, hybrid search).` : `Use fetch_url to look up documentation pages.`}
1061
1066
  - The Electric Agents docs site is at ${opts.docsUrl}
1062
1067
  - The docs site covers: Usage (entity definition, handlers, tools, state, spawning, coordination, waking, shared state, client integration, app setup), Reference (handler context, entity definitions, configurations, tools, state proxies, wake events, registries), Entities (Horton, Worker), and Patterns (Manager-Worker, Pipeline, Map-Reduce, Dispatcher, Blackboard, Reactive Observers).
1063
1068
  - For general coding questions unrelated to Electric Agents, use web_search or your own knowledge.` : ``;
@@ -1076,8 +1081,8 @@ When a user opens with a greeting ("hi", "hello", "hey", etc.) or a broad statem
1076
1081
  - web_search: search the web
1077
1082
  - fetch_url: fetch and convert a URL to markdown
1078
1083
  - spawn_worker: dispatch a subagent for an isolated task
1079
- - send: send a message to an Electric Agent/entity by entity URL
1080
- ${docsTools}${skillsTools}
1084
+ - send: send a message to an Electric Agent/entity. To schedule future work for yourself, call send with self: true and afterMs.
1085
+ ${eventSourceTools}${docsTools}${skillsTools}
1081
1086
 
1082
1087
  # Working with files
1083
1088
  - Prefer edit over write when modifying existing files.
@@ -1110,6 +1115,11 @@ Report outcomes faithfully. If a command failed, say so with the relevant output
1110
1115
  Working directory: ${workingDirectory}
1111
1116
  The current year is ${new Date().getFullYear()}.`;
1112
1117
  }
1118
+ function getToolName(tool) {
1119
+ if (typeof tool !== `object` || tool === null) return null;
1120
+ const name = tool.name;
1121
+ return typeof name === `string` ? name : null;
1122
+ }
1113
1123
  function createHortonTools(workingDirectory, ctx, readSet, opts = {}) {
1114
1124
  return [
1115
1125
  (0, __electric_ax_agents_runtime_tools.createBashTool)(workingDirectory),
@@ -1124,7 +1134,7 @@ function createHortonTools(workingDirectory, ctx, readSet, opts = {}) {
1124
1134
  logPrefix: opts.logPrefix ?? `[horton]`
1125
1135
  })] : [__electric_ax_agents_runtime_tools.fetchUrlTool],
1126
1136
  createSpawnWorkerTool(ctx, opts.modelConfig),
1127
- (0, __electric_ax_agents_runtime_tools.createSendTool)(ctx.send),
1137
+ (0, __electric_ax_agents_runtime_tools.createSendTool)(ctx.send, { selfEntityUrl: ctx.entityUrl }),
1128
1138
  ...opts.docsSearchTool ? [opts.docsSearchTool] : []
1129
1139
  ];
1130
1140
  }
@@ -1138,11 +1148,14 @@ function payloadToTitleText(payload) {
1138
1148
  return String(payload);
1139
1149
  }
1140
1150
  async function extractFirstUserMessage(ctx) {
1141
- const firstMessage = await (0, __durable_streams_state.queryOnce)((q) => q.from({ inbox: ctx.db.collections.inbox }).where(({ inbox }) => (0, __durable_streams_state.not)((0, __durable_streams_state.eq)(inbox.from, `system`))).orderBy(({ inbox }) => inbox._seq, `asc`).findOne());
1151
+ const firstMessage = ctx.db.collections.inbox.toArray.filter((message) => message.from !== `system`).sort((left, right) => messageSeq(left) - messageSeq(right))[0];
1142
1152
  if (!firstMessage) return null;
1143
1153
  const text = payloadToTitleText(firstMessage.payload);
1144
1154
  return text.length > 0 ? text : null;
1145
1155
  }
1156
+ function messageSeq(message) {
1157
+ return typeof message._seq === `number` ? message._seq : -1;
1158
+ }
1146
1159
  function readAgentsMd(workingDirectory) {
1147
1160
  const agentsMdPath = node_path.default.join(workingDirectory, `AGENTS.md`);
1148
1161
  try {
@@ -1176,6 +1189,7 @@ function createAssistantHandler(options) {
1176
1189
  ...skillsRegistry && skillsRegistry.catalog.size > 0 ? (0, __electric_ax_agents_runtime.createSkillTools)(skillsRegistry, ctx) : [],
1177
1190
  ...__electric_ax_agents_mcp.mcp.tools()
1178
1191
  ];
1192
+ const hasEventSourceTools = tools.some((tool) => getToolName(tool) === `list_event_sources`);
1179
1193
  const titlePromise = ctx.firstWake && !ctx.tags.title ? (async () => {
1180
1194
  const firstUserMessage = await extractFirstUserMessage(ctx);
1181
1195
  if (!firstUserMessage) return;
@@ -1262,7 +1276,8 @@ function createAssistantHandler(options) {
1262
1276
  hasSkills,
1263
1277
  docsUrl,
1264
1278
  modelProvider: modelConfig.provider,
1265
- modelId: String(modelConfig.model)
1279
+ modelId: String(modelConfig.model),
1280
+ hasEventSourceTools
1266
1281
  }),
1267
1282
  ...modelConfig,
1268
1283
  tools,
@@ -1302,15 +1317,7 @@ function registerHorton(registry, options) {
1302
1317
  creationSchema: hortonCreationSchema,
1303
1318
  handler: assistantHandler
1304
1319
  });
1305
- const typeNames = [`horton`];
1306
- if (streamFn) {
1307
- registry.define(`chat`, {
1308
- description: `Compatibility alias for the built-in assistant type.`,
1309
- handler: assistantHandler
1310
- });
1311
- typeNames.push(`chat`);
1312
- }
1313
- return typeNames;
1320
+ return [`horton`];
1314
1321
  }
1315
1322
 
1316
1323
  //#endregion
@@ -1374,7 +1381,7 @@ function buildToolsForWorker(tools, workingDirectory, ctx, readSet) {
1374
1381
  out.push(createSpawnWorkerTool(ctx));
1375
1382
  break;
1376
1383
  case `send`:
1377
- out.push((0, __electric_ax_agents_runtime_tools.createSendTool)(ctx.send));
1384
+ out.push((0, __electric_ax_agents_runtime_tools.createSendTool)(ctx.send, { selfEntityUrl: ctx.entityUrl }));
1378
1385
  break;
1379
1386
  }
1380
1387
  return out;
@@ -1503,6 +1510,27 @@ function registerWorker(registry, options) {
1503
1510
  //#endregion
1504
1511
  //#region src/bootstrap.ts
1505
1512
  const DEFAULT_BUILTIN_AGENT_HANDLER_PATH = `/_electric/builtin-agent-handler`;
1513
+ function toolName(tool) {
1514
+ return typeof tool.name === `string` ? tool.name : null;
1515
+ }
1516
+ function dedupeToolsByName(tools) {
1517
+ const seen = new Set();
1518
+ const deduped = [];
1519
+ for (const tool of tools) {
1520
+ const name = toolName(tool);
1521
+ if (name && seen.has(name)) continue;
1522
+ if (name) seen.add(name);
1523
+ deduped.push(tool);
1524
+ }
1525
+ return deduped;
1526
+ }
1527
+ function createBuiltinElectricTools(custom) {
1528
+ return async (context) => {
1529
+ const builtinTools = (0, __electric_ax_agents_runtime_tools.createEventSourceTools)(context);
1530
+ const customTools = custom ? await custom(context) : [];
1531
+ return dedupeToolsByName([...builtinTools, ...customTools]);
1532
+ };
1533
+ }
1506
1534
  async function createBuiltinAgentHandler(options) {
1507
1535
  const { agentServerUrl, serveEndpoint, workingDirectory, streamFn, createElectricTools, publicUrl, runtimeName, baseSkillsDir: baseSkillsDirOverride, serverHeaders, defaultDispatchPolicyForType } = options;
1508
1536
  const modelCatalog = await createBuiltinModelCatalog({ allowMockFallback: Boolean(streamFn) });
@@ -1545,7 +1573,7 @@ async function createBuiltinAgentHandler(options) {
1545
1573
  defaultDispatchPolicyForType,
1546
1574
  serverHeaders,
1547
1575
  idleTimeout: 5 * 6e4,
1548
- createElectricTools,
1576
+ createElectricTools: createBuiltinElectricTools(createElectricTools),
1549
1577
  publicUrl,
1550
1578
  name: runtimeName ?? `builtin-agents`
1551
1579
  });
@@ -1887,6 +1915,7 @@ Object.defineProperty(exports, 'braveSearchTool', {
1887
1915
  exports.buildHortonSystemPrompt = buildHortonSystemPrompt
1888
1916
  exports.createAgentHandler = createAgentHandler
1889
1917
  exports.createBuiltinAgentHandler = createBuiltinAgentHandler
1918
+ exports.createBuiltinElectricTools = createBuiltinElectricTools
1890
1919
  exports.createHortonDocsSupport = createHortonDocsSupport
1891
1920
  exports.createHortonTools = createHortonTools
1892
1921
  exports.createSpawnWorkerTool = createSpawnWorkerTool
package/dist/index.d.cts CHANGED
@@ -1,8 +1,8 @@
1
- import { AgentConfig, AgentTool, AvailableProvider, DispatchPolicy, EntityRegistry, EntityStreamDBWithActions, HandlerContext, HeadersProvider, PullWakeRunnerConfig, RuntimeHandler, SkillsRegistry, WakeEvent } from "@electric-ax/agents-runtime";
2
- import { ChangeEvent } from "@durable-streams/state";
1
+ import { AgentConfig, AgentTool, AvailableProvider, DispatchPolicy, EntityRegistry, HandlerContext, HeadersProvider, ProcessWakeConfig, PullWakeRunnerConfig, RuntimeHandler, SkillsRegistry, WakeEvent } from "@electric-ax/agents-runtime";
3
2
  import { AgentTool as AgentTool$1, StreamFn } from "@mariozechner/pi-agent-core";
4
3
  import { IncomingMessage, ServerResponse } from "node:http";
5
4
  import { ListedEntry as McpListedEntry, McpConfig, McpServerConfig, McpServerConfig as McpServerConfig$1, Registry, Registry as McpRegistry, RegistrySnapshot, RegistrySubscriber } from "@electric-ax/agents-mcp";
5
+ import { ChangeEvent } from "@durable-streams/state";
6
6
  import { braveSearchTool } from "@electric-ax/agents-runtime/tools";
7
7
 
8
8
  //#region src/bootstrap.d.ts
@@ -14,6 +14,7 @@ interface AgentHandlerResult {
14
14
  typeNames: Array<string>;
15
15
  skillsRegistry: SkillsRegistry | null;
16
16
  }
17
+ type BuiltinElectricToolsFactory = NonNullable<ProcessWakeConfig[`createElectricTools`]>;
17
18
  interface BuiltinAgentHandlerOptions {
18
19
  agentServerUrl: string;
19
20
  serveEndpoint?: string;
@@ -25,43 +26,15 @@ interface BuiltinAgentHandlerOptions {
25
26
  baseSkillsDir?: string;
26
27
  serverHeaders?: HeadersProvider;
27
28
  defaultDispatchPolicyForType?: (typeName: string) => DispatchPolicy | undefined;
28
- createElectricTools?: (context: {
29
- entityUrl: string;
30
- entityType: string;
31
- args: Readonly<Record<string, unknown>>;
32
- db: EntityStreamDBWithActions;
33
- events: Array<ChangeEvent>;
34
- upsertCronSchedule: (opts: {
35
- id: string;
36
- expression: string;
37
- timezone?: string;
38
- payload?: unknown;
39
- debounceMs?: number;
40
- timeoutMs?: number;
41
- }) => Promise<{
42
- txid: string;
43
- }>;
44
- upsertFutureSendSchedule: (opts: {
45
- id: string;
46
- payload: unknown;
47
- targetUrl?: string;
48
- fireAt: string;
49
- from?: string;
50
- messageType?: string;
51
- }) => Promise<{
52
- txid: string;
53
- }>;
54
- deleteSchedule: (opts: {
55
- id: string;
56
- }) => Promise<{
57
- txid: string;
58
- }>;
59
- }) => Array<AgentTool> | Promise<Array<AgentTool>>;
29
+ createElectricTools?: BuiltinElectricToolsFactory;
60
30
  }
31
+ declare function createBuiltinElectricTools(custom?: BuiltinElectricToolsFactory): BuiltinElectricToolsFactory;
61
32
  declare function createBuiltinAgentHandler(options: BuiltinAgentHandlerOptions): Promise<AgentHandlerResult | null>;
62
33
  declare function createAgentHandler(agentServerUrl: string, workingDirectory?: string, streamFn?: StreamFn, createElectricTools?: BuiltinAgentHandlerOptions[`createElectricTools`], serveEndpoint?: string): Promise<AgentHandlerResult | null>;
63
34
  declare function registerBuiltinAgentTypes(bootstrap: AgentHandlerResult): Promise<void>;
64
- declare const registerAgentTypes: typeof registerBuiltinAgentTypes; //#endregion
35
+ declare const registerAgentTypes: typeof registerBuiltinAgentTypes;
36
+
37
+ //#endregion
65
38
  //#region src/server.d.ts
66
39
  interface BuiltinAgentsServerOptions {
67
40
  agentServerUrl: string;
@@ -106,38 +79,7 @@ interface BuiltinAgentsServerOptions {
106
79
  loadProjectMcpConfig?: boolean;
107
80
  /** Override for the built-in skills directory; required when embedders bundle this package. */
108
81
  baseSkillsDir?: string;
109
- createElectricTools?: (context: {
110
- entityUrl: string;
111
- entityType: string;
112
- args: Readonly<Record<string, unknown>>;
113
- db: EntityStreamDBWithActions;
114
- events: Array<ChangeEvent>;
115
- upsertCronSchedule: (opts: {
116
- id: string;
117
- expression: string;
118
- timezone?: string;
119
- payload?: unknown;
120
- debounceMs?: number;
121
- timeoutMs?: number;
122
- }) => Promise<{
123
- txid: string;
124
- }>;
125
- upsertFutureSendSchedule: (opts: {
126
- id: string;
127
- payload: unknown;
128
- targetUrl?: string;
129
- fireAt: string;
130
- from?: string;
131
- messageType?: string;
132
- }) => Promise<{
133
- txid: string;
134
- }>;
135
- deleteSchedule: (opts: {
136
- id: string;
137
- }) => Promise<{
138
- txid: string;
139
- }>;
140
- }) => Array<AgentTool> | Promise<Array<AgentTool>>;
82
+ createElectricTools?: NonNullable<ProcessWakeConfig[`createElectricTools`]>;
141
83
  }
142
84
  declare class BuiltinAgentsServer {
143
85
  private bootstrap;
@@ -154,7 +96,9 @@ declare class BuiltinAgentsServer {
154
96
  start(): Promise<string>;
155
97
  stop(): Promise<void>;
156
98
  private registerPullWakeRunner;
157
- } //#endregion
99
+ }
100
+
101
+ //#endregion
158
102
  //#region src/entrypoint-lib.d.ts
159
103
  type EnvSource = Record<string, string | undefined>;
160
104
  interface BuiltinAgentsEntrypointOptions extends BuiltinAgentsServerOptions {}
@@ -206,6 +150,7 @@ declare const HORTON_MODEL = "claude-sonnet-4-6";
206
150
  declare function generateTitle(userMessage: string, llmCall: (prompt: string) => Promise<string>, onFallback?: (reason: string) => void): Promise<string>;
207
151
  declare function buildHortonSystemPrompt(workingDirectory: string, opts?: {
208
152
  hasDocsSupport?: boolean;
153
+ hasEventSourceTools?: boolean;
209
154
  hasSkills?: boolean;
210
155
  docsUrl?: string;
211
156
  modelProvider?: string;
@@ -258,4 +203,4 @@ declare function createHortonDocsSupport(workingDirectory: string, opts?: {
258
203
  }): HortonDocsSupport | null;
259
204
 
260
205
  //#endregion
261
- export { AgentHandlerResult, BuiltinAgentHandlerOptions, BuiltinAgentsEntrypointOptions, BuiltinAgentsEntrypointServer, BuiltinAgentsServer, BuiltinAgentsServerOptions, DEFAULT_BUILTIN_AGENT_HANDLER_PATH, HORTON_MODEL, McpConfig, McpListedEntry, McpRegistry, McpServerConfig, RegistrySnapshot, RegistrySubscriber, RunBuiltinAgentsEntrypointOptions, WORKER_TOOL_NAMES, WorkerToolName, braveSearchTool, buildHortonSystemPrompt, createAgentHandler, createBuiltinAgentHandler, createHortonDocsSupport, createHortonTools, createSpawnWorkerTool, generateTitle, registerAgentTypes, registerBuiltinAgentTypes, registerHorton, registerWorker, resolveBuiltinAgentsEntrypointOptions, runBuiltinAgentsEntrypoint };
206
+ export { AgentHandlerResult, BuiltinAgentHandlerOptions, BuiltinAgentsEntrypointOptions, BuiltinAgentsEntrypointServer, BuiltinAgentsServer, BuiltinAgentsServerOptions, BuiltinElectricToolsFactory, DEFAULT_BUILTIN_AGENT_HANDLER_PATH, HORTON_MODEL, McpConfig, McpListedEntry, McpRegistry, McpServerConfig, RegistrySnapshot, RegistrySubscriber, RunBuiltinAgentsEntrypointOptions, WORKER_TOOL_NAMES, WorkerToolName, braveSearchTool, buildHortonSystemPrompt, createAgentHandler, createBuiltinAgentHandler, createBuiltinElectricTools, createHortonDocsSupport, createHortonTools, createSpawnWorkerTool, generateTitle, registerAgentTypes, registerBuiltinAgentTypes, registerHorton, registerWorker, resolveBuiltinAgentsEntrypointOptions, runBuiltinAgentsEntrypoint };
package/dist/index.d.ts CHANGED
@@ -1,9 +1,9 @@
1
- import { AgentConfig, AgentTool, AvailableProvider, DispatchPolicy, EntityRegistry, EntityStreamDBWithActions, HandlerContext, HeadersProvider, PullWakeRunnerConfig, RuntimeHandler, SkillsRegistry, WakeEvent } from "@electric-ax/agents-runtime";
2
- import { ChangeEvent } from "@durable-streams/state";
1
+ import { AgentConfig, AgentTool, AvailableProvider, DispatchPolicy, EntityRegistry, HandlerContext, HeadersProvider, ProcessWakeConfig, PullWakeRunnerConfig, RuntimeHandler, SkillsRegistry, WakeEvent } from "@electric-ax/agents-runtime";
3
2
  import { braveSearchTool } from "@electric-ax/agents-runtime/tools";
4
3
  import { ListedEntry as McpListedEntry, McpConfig, McpServerConfig, McpServerConfig as McpServerConfig$1, Registry, Registry as McpRegistry, RegistrySnapshot, RegistrySubscriber } from "@electric-ax/agents-mcp";
5
4
  import { AgentTool as AgentTool$1, StreamFn } from "@mariozechner/pi-agent-core";
6
5
  import { IncomingMessage, ServerResponse } from "node:http";
6
+ import { ChangeEvent } from "@durable-streams/state";
7
7
 
8
8
  //#region src/bootstrap.d.ts
9
9
  declare const DEFAULT_BUILTIN_AGENT_HANDLER_PATH = "/_electric/builtin-agent-handler";
@@ -14,6 +14,7 @@ interface AgentHandlerResult {
14
14
  typeNames: Array<string>;
15
15
  skillsRegistry: SkillsRegistry | null;
16
16
  }
17
+ type BuiltinElectricToolsFactory = NonNullable<ProcessWakeConfig[`createElectricTools`]>;
17
18
  interface BuiltinAgentHandlerOptions {
18
19
  agentServerUrl: string;
19
20
  serveEndpoint?: string;
@@ -25,43 +26,15 @@ interface BuiltinAgentHandlerOptions {
25
26
  baseSkillsDir?: string;
26
27
  serverHeaders?: HeadersProvider;
27
28
  defaultDispatchPolicyForType?: (typeName: string) => DispatchPolicy | undefined;
28
- createElectricTools?: (context: {
29
- entityUrl: string;
30
- entityType: string;
31
- args: Readonly<Record<string, unknown>>;
32
- db: EntityStreamDBWithActions;
33
- events: Array<ChangeEvent>;
34
- upsertCronSchedule: (opts: {
35
- id: string;
36
- expression: string;
37
- timezone?: string;
38
- payload?: unknown;
39
- debounceMs?: number;
40
- timeoutMs?: number;
41
- }) => Promise<{
42
- txid: string;
43
- }>;
44
- upsertFutureSendSchedule: (opts: {
45
- id: string;
46
- payload: unknown;
47
- targetUrl?: string;
48
- fireAt: string;
49
- from?: string;
50
- messageType?: string;
51
- }) => Promise<{
52
- txid: string;
53
- }>;
54
- deleteSchedule: (opts: {
55
- id: string;
56
- }) => Promise<{
57
- txid: string;
58
- }>;
59
- }) => Array<AgentTool> | Promise<Array<AgentTool>>;
29
+ createElectricTools?: BuiltinElectricToolsFactory;
60
30
  }
31
+ declare function createBuiltinElectricTools(custom?: BuiltinElectricToolsFactory): BuiltinElectricToolsFactory;
61
32
  declare function createBuiltinAgentHandler(options: BuiltinAgentHandlerOptions): Promise<AgentHandlerResult | null>;
62
33
  declare function createAgentHandler(agentServerUrl: string, workingDirectory?: string, streamFn?: StreamFn, createElectricTools?: BuiltinAgentHandlerOptions[`createElectricTools`], serveEndpoint?: string): Promise<AgentHandlerResult | null>;
63
34
  declare function registerBuiltinAgentTypes(bootstrap: AgentHandlerResult): Promise<void>;
64
- declare const registerAgentTypes: typeof registerBuiltinAgentTypes; //#endregion
35
+ declare const registerAgentTypes: typeof registerBuiltinAgentTypes;
36
+
37
+ //#endregion
65
38
  //#region src/server.d.ts
66
39
  interface BuiltinAgentsServerOptions {
67
40
  agentServerUrl: string;
@@ -106,38 +79,7 @@ interface BuiltinAgentsServerOptions {
106
79
  loadProjectMcpConfig?: boolean;
107
80
  /** Override for the built-in skills directory; required when embedders bundle this package. */
108
81
  baseSkillsDir?: string;
109
- createElectricTools?: (context: {
110
- entityUrl: string;
111
- entityType: string;
112
- args: Readonly<Record<string, unknown>>;
113
- db: EntityStreamDBWithActions;
114
- events: Array<ChangeEvent>;
115
- upsertCronSchedule: (opts: {
116
- id: string;
117
- expression: string;
118
- timezone?: string;
119
- payload?: unknown;
120
- debounceMs?: number;
121
- timeoutMs?: number;
122
- }) => Promise<{
123
- txid: string;
124
- }>;
125
- upsertFutureSendSchedule: (opts: {
126
- id: string;
127
- payload: unknown;
128
- targetUrl?: string;
129
- fireAt: string;
130
- from?: string;
131
- messageType?: string;
132
- }) => Promise<{
133
- txid: string;
134
- }>;
135
- deleteSchedule: (opts: {
136
- id: string;
137
- }) => Promise<{
138
- txid: string;
139
- }>;
140
- }) => Array<AgentTool> | Promise<Array<AgentTool>>;
82
+ createElectricTools?: NonNullable<ProcessWakeConfig[`createElectricTools`]>;
141
83
  }
142
84
  declare class BuiltinAgentsServer {
143
85
  private bootstrap;
@@ -154,7 +96,9 @@ declare class BuiltinAgentsServer {
154
96
  start(): Promise<string>;
155
97
  stop(): Promise<void>;
156
98
  private registerPullWakeRunner;
157
- } //#endregion
99
+ }
100
+
101
+ //#endregion
158
102
  //#region src/entrypoint-lib.d.ts
159
103
  type EnvSource = Record<string, string | undefined>;
160
104
  interface BuiltinAgentsEntrypointOptions extends BuiltinAgentsServerOptions {}
@@ -206,6 +150,7 @@ declare const HORTON_MODEL = "claude-sonnet-4-6";
206
150
  declare function generateTitle(userMessage: string, llmCall: (prompt: string) => Promise<string>, onFallback?: (reason: string) => void): Promise<string>;
207
151
  declare function buildHortonSystemPrompt(workingDirectory: string, opts?: {
208
152
  hasDocsSupport?: boolean;
153
+ hasEventSourceTools?: boolean;
209
154
  hasSkills?: boolean;
210
155
  docsUrl?: string;
211
156
  modelProvider?: string;
@@ -258,4 +203,4 @@ declare function createHortonDocsSupport(workingDirectory: string, opts?: {
258
203
  }): HortonDocsSupport | null;
259
204
 
260
205
  //#endregion
261
- export { AgentHandlerResult, BuiltinAgentHandlerOptions, BuiltinAgentsEntrypointOptions, BuiltinAgentsEntrypointServer, BuiltinAgentsServer, BuiltinAgentsServerOptions, DEFAULT_BUILTIN_AGENT_HANDLER_PATH, HORTON_MODEL, McpConfig, McpListedEntry, McpRegistry, McpServerConfig, RegistrySnapshot, RegistrySubscriber, RunBuiltinAgentsEntrypointOptions, WORKER_TOOL_NAMES, WorkerToolName, braveSearchTool, buildHortonSystemPrompt, createAgentHandler, createBuiltinAgentHandler, createHortonDocsSupport, createHortonTools, createSpawnWorkerTool, generateTitle, registerAgentTypes, registerBuiltinAgentTypes, registerHorton, registerWorker, resolveBuiltinAgentsEntrypointOptions, runBuiltinAgentsEntrypoint };
206
+ export { AgentHandlerResult, BuiltinAgentHandlerOptions, BuiltinAgentsEntrypointOptions, BuiltinAgentsEntrypointServer, BuiltinAgentsServer, BuiltinAgentsServerOptions, BuiltinElectricToolsFactory, DEFAULT_BUILTIN_AGENT_HANDLER_PATH, HORTON_MODEL, McpConfig, McpListedEntry, McpRegistry, McpServerConfig, RegistrySnapshot, RegistrySubscriber, RunBuiltinAgentsEntrypointOptions, WORKER_TOOL_NAMES, WorkerToolName, braveSearchTool, buildHortonSystemPrompt, createAgentHandler, createBuiltinAgentHandler, createBuiltinElectricTools, createHortonDocsSupport, createHortonTools, createSpawnWorkerTool, generateTitle, registerAgentTypes, registerBuiltinAgentTypes, registerHorton, registerWorker, resolveBuiltinAgentsEntrypointOptions, runBuiltinAgentsEntrypoint };
package/dist/index.js CHANGED
@@ -2,9 +2,9 @@ import { mergeElectricPrincipalHeader } from "./server-headers-KD5yHFYT.js";
2
2
  import path from "node:path";
3
3
  import { fileURLToPath } from "node:url";
4
4
  import { appendPathToUrl, completeWithLowCostModel, createEntityRegistry, createPullWakeRunner, createRuntimeHandler, createSkillTools, createSkillsRegistry, db, detectAvailableProviders, readCodexAccessToken, registerToolProvider, unregisterToolProvider } from "@electric-ax/agents-runtime";
5
+ import { braveSearchTool, braveSearchTool as braveSearchTool$1, createBashTool, createEditTool, createEventSourceTools, createFetchUrlTool, createReadFileTool, createSendTool, createWriteTool, fetchUrlTool } from "@electric-ax/agents-runtime/tools";
5
6
  import fs from "node:fs";
6
7
  import pino from "pino";
7
- import { eq, not, queryOnce } from "@durable-streams/state";
8
8
  import { z } from "zod";
9
9
  import { createHash } from "node:crypto";
10
10
  import fs$1 from "node:fs/promises";
@@ -13,7 +13,6 @@ import { Type } from "@sinclair/typebox";
13
13
  import { load } from "sqlite-vec";
14
14
  import { nanoid } from "nanoid";
15
15
  import { getModels } from "@mariozechner/pi-ai";
16
- import { braveSearchTool, braveSearchTool as braveSearchTool$1, createBashTool, createEditTool, createFetchUrlTool, createReadFileTool, createSendTool, createWriteTool, fetchUrlTool } from "@electric-ax/agents-runtime/tools";
17
16
  import { bridgeMcpTool, buildPromptTools, buildResourceTools, createRegistry, keychainPersistence, loadConfig, mcp, watchConfig } from "@electric-ax/agents-mcp";
18
17
 
19
18
  //#region src/log.ts
@@ -652,9 +651,9 @@ function createHortonDocsSupport(workingDirectory, opts = {}) {
652
651
  },
653
652
  createSearchTool() {
654
653
  return {
655
- name: `search_durable_agents_docs`,
656
- label: `Search Durable Agents Docs`,
657
- description: `Run a hybrid BM25 plus vector search over the local Durable Agents documentation index.`,
654
+ name: `search_electric_agents_docs`,
655
+ label: `Search Electric Agents Docs`,
656
+ description: `Run a hybrid BM25 plus vector search over the local Electric Agents documentation index.`,
658
657
  parameters: Type.Object({
659
658
  query: Type.String({ description: `The docs question or search query to run.` }),
660
659
  limit: Type.Optional(Type.Number({
@@ -785,12 +784,14 @@ const REASONING_EFFORT_VALUES = [
785
784
  const DEFAULT_ANTHROPIC_MODEL = `claude-sonnet-4-6`;
786
785
  const DEFAULT_OPENAI_MODEL = `gpt-4.1`;
787
786
  const DEFAULT_CODEX_MODEL = `gpt-5.4`;
787
+ const DEFAULT_DEEPSEEK_MODEL = `deepseek-v4-flash`;
788
788
  function modelValue(provider, id) {
789
789
  return `${provider}:${id}`;
790
790
  }
791
791
  function providerLabel(provider) {
792
792
  if (provider === `anthropic`) return `Anthropic`;
793
793
  if (provider === `openai-codex`) return `OpenAI Codex`;
794
+ if (provider === `deepseek`) return `DeepSeek`;
794
795
  return `OpenAI`;
795
796
  }
796
797
  function configuredProviders() {
@@ -817,6 +818,9 @@ async function fetchAvailableModelIds(provider) {
817
818
  "anthropic-version": `2023-06-01`
818
819
  },
819
820
  signal: AbortSignal.timeout(3e3)
821
+ }) : provider === `deepseek` ? await fetch(`https://api.deepseek.com/v1/models`, {
822
+ headers: { authorization: `Bearer ${process.env.DEEPSEEK_API_KEY ?? ``}` },
823
+ signal: AbortSignal.timeout(3e3)
820
824
  }) : await fetch(`https://api.openai.com/v1/models`, {
821
825
  headers: { authorization: `Bearer ${process.env.OPENAI_API_KEY ?? ``}` },
822
826
  signal: AbortSignal.timeout(3e3)
@@ -877,7 +881,7 @@ async function createBuiltinModelCatalog(options = {}) {
877
881
  if (providers.length === 0 && options.allowMockFallback) return mockFallbackCatalog();
878
882
  const choices = (await Promise.all(providers.map((provider) => choicesForProvider(provider)))).flat();
879
883
  if (choices.length === 0) return options.allowMockFallback ? mockFallbackCatalog() : null;
880
- const defaultChoice = choices.find((choice) => choice.provider === `anthropic` && choice.id === DEFAULT_ANTHROPIC_MODEL) ?? choices.find((choice) => choice.provider === `openai` && choice.id === DEFAULT_OPENAI_MODEL) ?? choices.find((choice) => choice.provider === `openai-codex` && choice.id === DEFAULT_CODEX_MODEL) ?? choices[0];
884
+ const defaultChoice = choices.find((choice) => choice.provider === `anthropic` && choice.id === DEFAULT_ANTHROPIC_MODEL) ?? choices.find((choice) => choice.provider === `openai` && choice.id === DEFAULT_OPENAI_MODEL) ?? choices.find((choice) => choice.provider === `openai-codex` && choice.id === DEFAULT_CODEX_MODEL) ?? choices.find((choice) => choice.provider === `deepseek` && choice.id === DEFAULT_DEEPSEEK_MODEL) ?? choices[0];
881
885
  return {
882
886
  choices,
883
887
  defaultChoice
@@ -998,9 +1002,10 @@ async function generateTitle(userMessage, llmCall, onFallback) {
998
1002
  }
999
1003
  }
1000
1004
  function buildHortonSystemPrompt(workingDirectory, opts = {}) {
1001
- const docsTools = opts.hasDocsSupport ? `\n- search_durable_agents_docs: hybrid search over the built-in Durable Agents docs index` : ``;
1005
+ const docsTools = opts.hasDocsSupport ? `\n- search_electric_agents_docs: hybrid search over the built-in Electric Agents docs index` : ``;
1006
+ const eventSourceTools = opts.hasEventSourceTools ? `\n- list_event_sources: list external webhook/event feeds you can subscribe to, including available buckets and parameters\n- subscribe_event_source: subscribe yourself to one of those feeds or buckets so matching future events wake you\n- list_event_source_subscriptions: list your active event source subscriptions\n- unsubscribe_event_source: remove one of your event source subscriptions by id` : ``;
1002
1007
  const skillsTools = opts.hasSkills ? `\n- use_skill: load a skill (knowledge, instructions, or a tutorial) into your context to help with the user's request\n- remove_skill: unload a skill from context when you're done with it` : ``;
1003
- const docsGuidance = opts.hasDocsSupport ? `\n- For ANY question about Electric Agents, Durable Agents, or this framework, ALWAYS use search_durable_agents_docs FIRST. Do not use web_search or fetch_url for Electric Agents topics unless the docs search returns no useful results.\n- The search tool returns chunk content directly — you do not need to read the source files.\n- Use repo read/bash tools only for non-doc files or when you need to inspect exact implementation code in the workspace.` : ``;
1008
+ const docsGuidance = opts.hasDocsSupport ? `\n- For ANY question about Electric Agents or this framework, ALWAYS use search_electric_agents_docs FIRST. Do not use web_search or fetch_url for Electric Agents topics unless the docs search returns no useful results.\n- The search tool returns chunk content directly — you do not need to read the source files.\n- Use repo read/bash tools only for non-doc files or when you need to inspect exact implementation code in the workspace.` : ``;
1004
1009
  const skillsGuidance = opts.hasSkills ? `\n# Skills\nYou have access to skills — specialized knowledge and guided workflows you can load on demand. Your context includes a skills catalog listing what's available. When the user's request matches a skill's description or keywords, load it with use_skill.
1005
1010
 
1006
1011
  Some skills are user-invocable — the user can trigger them with a slash command like \`/quickstart\`. When you see a message starting with \`/\` followed by a skill name, load that skill immediately with use_skill. Pass any text after the skill name as args.
@@ -1019,7 +1024,7 @@ Do NOT load a skill and then ignore its instructions. The skill is there because
1019
1024
  When a user is new or asks how to get started with Electric Agents, **don't assume a single path**. Present the options and let them choose:
1020
1025
 
1021
1026
  - **Learn the concepts first** → Explain what Electric Agents is, answer questions, point to docs.
1022
- Use search_durable_agents_docs to look up answers. Only load the quickstart skill if the user explicitly asks for a hands-on guided tutorial.
1027
+ Use search_electric_agents_docs to look up answers. Only load the quickstart skill if the user explicitly asks for a hands-on guided tutorial.
1023
1028
 
1024
1029
  - **Hands-on guided tutorial** → Load the quickstart skill (or tell them to type \`/quickstart\`).
1025
1030
  This is a step-by-step build that takes them from zero to a running app.
@@ -1029,11 +1034,11 @@ When a user is new or asks how to get started with Electric Agents, **don't assu
1029
1034
  This sets up project structure and orients them in the codebase.
1030
1035
 
1031
1036
  - **Have a specific question?** → Answer it directly.
1032
- Use search_durable_agents_docs first, then fall back to fetch_url or general knowledge if needed.
1037
+ Use search_electric_agents_docs first, then fall back to fetch_url or general knowledge if needed.
1033
1038
 
1034
1039
  Don't force onboarding. If someone just wants to chat or code, let them. When in doubt, ask what they'd like to do rather than picking a path for them.`;
1035
1040
  const docsUrlGuidance = opts.docsUrl ? `\n# Electric Agents documentation
1036
- - ${opts.hasDocsSupport ? `If search_durable_agents_docs is available, use it first (faster, hybrid search).` : `Use fetch_url to look up documentation pages.`}
1041
+ - ${opts.hasDocsSupport ? `If search_electric_agents_docs is available, use it first (faster, hybrid search).` : `Use fetch_url to look up documentation pages.`}
1037
1042
  - The Electric Agents docs site is at ${opts.docsUrl}
1038
1043
  - The docs site covers: Usage (entity definition, handlers, tools, state, spawning, coordination, waking, shared state, client integration, app setup), Reference (handler context, entity definitions, configurations, tools, state proxies, wake events, registries), Entities (Horton, Worker), and Patterns (Manager-Worker, Pipeline, Map-Reduce, Dispatcher, Blackboard, Reactive Observers).
1039
1044
  - For general coding questions unrelated to Electric Agents, use web_search or your own knowledge.` : ``;
@@ -1052,8 +1057,8 @@ When a user opens with a greeting ("hi", "hello", "hey", etc.) or a broad statem
1052
1057
  - web_search: search the web
1053
1058
  - fetch_url: fetch and convert a URL to markdown
1054
1059
  - spawn_worker: dispatch a subagent for an isolated task
1055
- - send: send a message to an Electric Agent/entity by entity URL
1056
- ${docsTools}${skillsTools}
1060
+ - send: send a message to an Electric Agent/entity. To schedule future work for yourself, call send with self: true and afterMs.
1061
+ ${eventSourceTools}${docsTools}${skillsTools}
1057
1062
 
1058
1063
  # Working with files
1059
1064
  - Prefer edit over write when modifying existing files.
@@ -1086,6 +1091,11 @@ Report outcomes faithfully. If a command failed, say so with the relevant output
1086
1091
  Working directory: ${workingDirectory}
1087
1092
  The current year is ${new Date().getFullYear()}.`;
1088
1093
  }
1094
+ function getToolName(tool) {
1095
+ if (typeof tool !== `object` || tool === null) return null;
1096
+ const name = tool.name;
1097
+ return typeof name === `string` ? name : null;
1098
+ }
1089
1099
  function createHortonTools(workingDirectory, ctx, readSet, opts = {}) {
1090
1100
  return [
1091
1101
  createBashTool(workingDirectory),
@@ -1100,7 +1110,7 @@ function createHortonTools(workingDirectory, ctx, readSet, opts = {}) {
1100
1110
  logPrefix: opts.logPrefix ?? `[horton]`
1101
1111
  })] : [fetchUrlTool],
1102
1112
  createSpawnWorkerTool(ctx, opts.modelConfig),
1103
- createSendTool(ctx.send),
1113
+ createSendTool(ctx.send, { selfEntityUrl: ctx.entityUrl }),
1104
1114
  ...opts.docsSearchTool ? [opts.docsSearchTool] : []
1105
1115
  ];
1106
1116
  }
@@ -1114,11 +1124,14 @@ function payloadToTitleText(payload) {
1114
1124
  return String(payload);
1115
1125
  }
1116
1126
  async function extractFirstUserMessage(ctx) {
1117
- const firstMessage = await queryOnce((q) => q.from({ inbox: ctx.db.collections.inbox }).where(({ inbox }) => not(eq(inbox.from, `system`))).orderBy(({ inbox }) => inbox._seq, `asc`).findOne());
1127
+ const firstMessage = ctx.db.collections.inbox.toArray.filter((message) => message.from !== `system`).sort((left, right) => messageSeq(left) - messageSeq(right))[0];
1118
1128
  if (!firstMessage) return null;
1119
1129
  const text = payloadToTitleText(firstMessage.payload);
1120
1130
  return text.length > 0 ? text : null;
1121
1131
  }
1132
+ function messageSeq(message) {
1133
+ return typeof message._seq === `number` ? message._seq : -1;
1134
+ }
1122
1135
  function readAgentsMd(workingDirectory) {
1123
1136
  const agentsMdPath = path.join(workingDirectory, `AGENTS.md`);
1124
1137
  try {
@@ -1152,6 +1165,7 @@ function createAssistantHandler(options) {
1152
1165
  ...skillsRegistry && skillsRegistry.catalog.size > 0 ? createSkillTools(skillsRegistry, ctx) : [],
1153
1166
  ...mcp.tools()
1154
1167
  ];
1168
+ const hasEventSourceTools = tools.some((tool) => getToolName(tool) === `list_event_sources`);
1155
1169
  const titlePromise = ctx.firstWake && !ctx.tags.title ? (async () => {
1156
1170
  const firstUserMessage = await extractFirstUserMessage(ctx);
1157
1171
  if (!firstUserMessage) return;
@@ -1238,7 +1252,8 @@ function createAssistantHandler(options) {
1238
1252
  hasSkills,
1239
1253
  docsUrl,
1240
1254
  modelProvider: modelConfig.provider,
1241
- modelId: String(modelConfig.model)
1255
+ modelId: String(modelConfig.model),
1256
+ hasEventSourceTools
1242
1257
  }),
1243
1258
  ...modelConfig,
1244
1259
  tools,
@@ -1278,15 +1293,7 @@ function registerHorton(registry, options) {
1278
1293
  creationSchema: hortonCreationSchema,
1279
1294
  handler: assistantHandler
1280
1295
  });
1281
- const typeNames = [`horton`];
1282
- if (streamFn) {
1283
- registry.define(`chat`, {
1284
- description: `Compatibility alias for the built-in assistant type.`,
1285
- handler: assistantHandler
1286
- });
1287
- typeNames.push(`chat`);
1288
- }
1289
- return typeNames;
1296
+ return [`horton`];
1290
1297
  }
1291
1298
 
1292
1299
  //#endregion
@@ -1350,7 +1357,7 @@ function buildToolsForWorker(tools, workingDirectory, ctx, readSet) {
1350
1357
  out.push(createSpawnWorkerTool(ctx));
1351
1358
  break;
1352
1359
  case `send`:
1353
- out.push(createSendTool(ctx.send));
1360
+ out.push(createSendTool(ctx.send, { selfEntityUrl: ctx.entityUrl }));
1354
1361
  break;
1355
1362
  }
1356
1363
  return out;
@@ -1479,6 +1486,27 @@ function registerWorker(registry, options) {
1479
1486
  //#endregion
1480
1487
  //#region src/bootstrap.ts
1481
1488
  const DEFAULT_BUILTIN_AGENT_HANDLER_PATH = `/_electric/builtin-agent-handler`;
1489
+ function toolName(tool) {
1490
+ return typeof tool.name === `string` ? tool.name : null;
1491
+ }
1492
+ function dedupeToolsByName(tools) {
1493
+ const seen = new Set();
1494
+ const deduped = [];
1495
+ for (const tool of tools) {
1496
+ const name = toolName(tool);
1497
+ if (name && seen.has(name)) continue;
1498
+ if (name) seen.add(name);
1499
+ deduped.push(tool);
1500
+ }
1501
+ return deduped;
1502
+ }
1503
+ function createBuiltinElectricTools(custom) {
1504
+ return async (context) => {
1505
+ const builtinTools = createEventSourceTools(context);
1506
+ const customTools = custom ? await custom(context) : [];
1507
+ return dedupeToolsByName([...builtinTools, ...customTools]);
1508
+ };
1509
+ }
1482
1510
  async function createBuiltinAgentHandler(options) {
1483
1511
  const { agentServerUrl, serveEndpoint, workingDirectory, streamFn, createElectricTools, publicUrl, runtimeName, baseSkillsDir: baseSkillsDirOverride, serverHeaders, defaultDispatchPolicyForType } = options;
1484
1512
  const modelCatalog = await createBuiltinModelCatalog({ allowMockFallback: Boolean(streamFn) });
@@ -1521,7 +1549,7 @@ async function createBuiltinAgentHandler(options) {
1521
1549
  defaultDispatchPolicyForType,
1522
1550
  serverHeaders,
1523
1551
  idleTimeout: 5 * 6e4,
1524
- createElectricTools,
1552
+ createElectricTools: createBuiltinElectricTools(createElectricTools),
1525
1553
  publicUrl,
1526
1554
  name: runtimeName ?? `builtin-agents`
1527
1555
  });
@@ -1850,4 +1878,4 @@ async function runBuiltinAgentsEntrypoint({ env = process.env, cwd = process.cwd
1850
1878
  }
1851
1879
 
1852
1880
  //#endregion
1853
- export { BuiltinAgentsServer, DEFAULT_BUILTIN_AGENT_HANDLER_PATH, HORTON_MODEL, WORKER_TOOL_NAMES, braveSearchTool, buildHortonSystemPrompt, createAgentHandler, createBuiltinAgentHandler, createHortonDocsSupport, createHortonTools, createSpawnWorkerTool, generateTitle, registerAgentTypes, registerBuiltinAgentTypes, registerHorton, registerWorker, resolveBuiltinAgentsEntrypointOptions, runBuiltinAgentsEntrypoint };
1881
+ export { BuiltinAgentsServer, DEFAULT_BUILTIN_AGENT_HANDLER_PATH, HORTON_MODEL, WORKER_TOOL_NAMES, braveSearchTool, buildHortonSystemPrompt, createAgentHandler, createBuiltinAgentHandler, createBuiltinElectricTools, createHortonDocsSupport, createHortonTools, createSpawnWorkerTool, generateTitle, registerAgentTypes, registerBuiltinAgentTypes, registerHorton, registerWorker, resolveBuiltinAgentsEntrypointOptions, runBuiltinAgentsEntrypoint };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@electric-ax/agents",
3
- "version": "0.4.6",
3
+ "version": "0.4.9",
4
4
  "description": "Built-in Electric Agents runtimes such as Horton and worker",
5
5
  "repository": {
6
6
  "type": "git",
@@ -49,7 +49,7 @@
49
49
  "sqlite-vec": "^0.1.9",
50
50
  "zod": "^4.3.6",
51
51
  "@electric-ax/agents-mcp": "0.2.2",
52
- "@electric-ax/agents-runtime": "0.3.2"
52
+ "@electric-ax/agents-runtime": "0.3.5"
53
53
  },
54
54
  "devDependencies": {
55
55
  "@types/better-sqlite3": "^7.6.13",
@@ -58,7 +58,7 @@
58
58
  "cross-env": "^10.1.0",
59
59
  "tsdown": "^0.9.0",
60
60
  "tsx": "^4.19.0",
61
- "typescript": "^5.0.0",
61
+ "typescript": "^5.9.3",
62
62
  "vitest": "^4.1.0"
63
63
  },
64
64
  "files": [
@@ -100,7 +100,7 @@ IMPORTANT: Never write files until the user explicitly confirms. "Ask to write"
100
100
 
101
101
  - **Continue building** — add an HTTP API route and a React frontend to this app so users can interact with the analyzer from the browser.
102
102
  - **Start a new app** — use the `agents-chat-starter` template for a full multi-agent chat app with rooms, agent spawning, and a Slack-style UI. Load the init skill or tell them to type `/init`.
103
- - **Explore the docs** — read about other coordination patterns (blackboard, pipeline, map-reduce), dive into the API reference, or learn about shared state, context assembly, and other advanced features. Use `search_durable_agents_docs` to look things up.
103
+ - **Explore the docs** — read about other coordination patterns (blackboard, pipeline, map-reduce), dive into the API reference, or learn about shared state, context assembly, and other advanced features. Use `search_electric_agents_docs` to look things up.
104
104
 
105
105
  Wait for the user to choose. Only proceed to Step 4 if they want to continue building.
106
106
 
@@ -116,7 +116,7 @@ Wait for the user to choose. Only proceed to Step 4 if they want to continue bui
116
116
  - `server.ts` is at the working directory root. Entity files go in `entities/`.
117
117
  - Worker spawn args MUST include `tools` array (at least one tool, e.g. `tools: ["bash"]`).
118
118
  - Use `edit` tool for small changes (like updating server.ts). Use `write` for full entity file updates.
119
- - If the user asks a question about Electric Agents concepts, APIs, or patterns between steps, use the `search_durable_agents_docs` tool to look up the answer in the built-in documentation before guessing or searching the web.
119
+ - If the user asks a question about Electric Agents concepts, APIs, or patterns between steps, use the `search_electric_agents_docs` tool to look up the answer in the built-in documentation before guessing or searching the web.
120
120
 
121
121
  ---
122
122