@electric-ax/agents-runtime

中文文档
0.6.3发布于 2026/06/26Apache-2.0TypeScriptESM + CJS构建来源已验证近 30 天下载 1.1万

Electric agent runtime — behavioral stack for agent entities over durable streams

原始使用文档
文档目录14 节
  1. Install
  2. Quick Start
  3. Entity Model
  4. HandlerContext
  5. State
  6. Agents
  7. Spawn, Observe, and Send
  8. Shared State
  9. Runtime Handler
  10. Entity Signals
  11. Built-in agents
  12. Timeline Helpers
  13. Testing
  14. License

@electric-ax/agents-runtime

Electric Agents runtime for durable entity handlers over Durable Streams.

Each entity owns an append-only stream. On every wake, the runtime materializes that stream into typed TanStack DB collections, builds a HandlerContext, and runs your entity's single handler(ctx, wake) entrypoint.

There is no separate setup() or loader() phase in the current API.

Install

pnpm add @electric-ax/agents-runtime

Peer dependency: @tanstack/db >= 0.5.33

Quick Start

import http from 'node:http'
import { z } from 'zod'
import { createRuntimeHandler, defineEntity } from '@electric-ax/agents-runtime'

defineEntity(`assistant`, {
  description: `Simple durable chat assistant`,

  state: {
    status: {
      schema: z.object({
        key: z.string(),
        value: z.enum([`idle`, `working`]),
      }),
      type: `status`,
      primaryKey: `key`,
    },
  },

  async handler(ctx) {
    if (!ctx.db.collections.status.get(`current`)) {
      ctx.db.actions.status_insert({
        row: { key: `current`, value: `idle` },
      })
    }

    ctx.useAgent({
      systemPrompt: `You are a helpful assistant.`,
      model: `claude-sonnet-4-5-20250929`,
      tools: [...ctx.electricTools],
    })

    await ctx.agent.run()
  },
})

const runtime = createRuntimeHandler({
  baseUrl: `http://127.0.0.1:4437`,
  serveEndpoint: `http://127.0.0.1:3000/webhook`,
})

await runtime.registerTypes()

const server = http.createServer(async (req, res) => {
  if (req.url === `/webhook` && req.method === `POST`) {
    await runtime.onEnter(req, res)
    return
  }

  res.writeHead(404)
  res.end()
})

server.listen(3000, `127.0.0.1`)

Entity Model

Entities are declared with defineEntity(name, definition):

import { z } from 'zod'
import { defineEntity } from '@electric-ax/agents-runtime'

defineEntity(`pr-reviewer`, {
  description: `Code review agent`,

  creationSchema: z.object({
    repo: z.string(),
    prNumber: z.number().int(),
  }),

  inboxSchemas: {
    'pr-opened': z.object({
      diff: z.string(),
      baseBranch: z.string().optional(),
    }),
  },

  state: {
    reviewStatus: {
      schema: z.object({
        key: z.string(),
        status: z.enum([`pending`, `reviewing`, `done`]),
      }),
      type: `review_status`,
      primaryKey: `key`,
    },
  },

  async handler(ctx, wake) {
    if (ctx.firstWake && !ctx.db.collections.reviewStatus.get(`current`)) {
      ctx.db.actions.reviewStatus_insert({
        row: { key: `current`, status: `reviewing` },
      })
    }

    if (wake.type === `inbox`) {
      ctx.useAgent({
        systemPrompt: `Review pull requests carefully and concisely.`,
        model: `claude-sonnet-4-5-20250929`,
        tools: [...ctx.electricTools],
      })
      await ctx.agent.run()
      ctx.db.actions.reviewStatus_update({
        key: `current`,
        updater: (draft) => {
          draft.status = `done`
        },
      })
    }
  },
})

HandlerContext

handler(ctx, wake) receives:

  • ctx.firstWake — true only on the first wake for the entity
  • ctx.entityUrl / ctx.entityType — identity metadata
  • ctx.args — immutable validated spawn args
  • ctx.db — the entity's materialized StreamDB exposing typed ctx.db.collections.<name> (TanStack DB collections) for reads and auto-generated write actions at ctx.db.actions.<name>_{insert,update,delete}
  • ctx.electricTools — built-in runtime tools to pass through to agents

State

Every collection declared under definition.state gets auto-generated <name>_insert, <name>_update, and <name>_delete actions on ctx.db.actions. Reads go through ctx.db.collections.<name>:

ctx.db.actions.counts_insert({ row: { key: `main`, value: 0 } })
ctx.db.actions.counts_update({
  key: `main`,
  updater: (draft) => {
    draft.value++
  },
})
ctx.db.actions.counts_delete({ key: `main` })

ctx.db.collections.counts.get(`main`)
ctx.db.collections.counts.toArray

Agents

ctx.useAgent({
  systemPrompt: `You are a helpful assistant.`,
  model: `claude-sonnet-4-5-20250929`,
  tools: [...ctx.electricTools, myTool],
})

await ctx.agent.run()

agent.run() assembles the entity's context from the materialized timeline and processes the wake's trigger message. If you do not call useAgent(), the runtime will not run an LLM for that wake.

Spawn, Observe, and Send

const child = await ctx.spawn(
  `researcher`,
  `r-1`,
  { topic: `durability` },
  { wake: { on: `runFinished`, includeResponse: true } }
)

// Return from this wake. Continue when the child completion wake arrives.

const observed = await ctx.observe(entity(`/researcher/r-2`), {
  wake: { on: `runFinished`, includeResponse: true },
})
const status = observed.status()

ctx.send(`/other-entity/id`, { text: `hello` }, { type: `message` })

Shared State

const boardSchema = {
  findings: {
    schema: z.object({
      key: z.string(),
      domain: z.string(),
      finding: z.string(),
    }),
    type: `finding`,
    primaryKey: `key`,
  },
}

if (ctx.firstWake) {
  ctx.mkdb(`board-1`, boardSchema)
}

const board = await ctx.observe(db(`board-1`, boardSchema))

board.findings.insert({
  key: `f-1`,
  domain: `security`,
  finding: `XSS found`,
})

mkdb creates the backing stream (throws if it already exists). observe(db(...)) returns a handle for reading and writing on any wake.

Runtime Handler

createRuntimeHandler() creates the webhook entrypoint that Electric Agents calls when an entity is woken.

const runtime = createRuntimeHandler({
  baseUrl: `http://127.0.0.1:4437`,
  serveEndpoint: `http://127.0.0.1:3000/webhook`,
})

await runtime.registerTypes()

Main methods:

  • runtime.registerTypes() — register all entity types with the server
  • runtime.onEnter(req, res) — Node HTTP adapter for webhook delivery
  • runtime.handleRequest(request) — fetch-native handler
  • runtime.drainWakes() — wait for in-flight wakes to settle

Entity Signals

Agents can be controlled through the server lifecycle endpoint:

POST /_electric/entities/:type/:instanceId/signal

The JSON body is { "signal": "SIGINT", "reason": "...", "payload": ... }. The same API is exposed by the CLI:

electric agents signal /horton/onboarding SIGINT --reason "stop current run"
electric agents kill /horton/onboarding

Signal behavior:

  • SIGINT aborts the active handler invocation and leaves the entity state unchanged.
  • Non-agent handlers can observe this cancellation through ctx.signal and pass it to cancellable work such as fetch or subprocesses.
  • SIGSTOP pauses the entity. New messages wake it again, but already pending work is not processed while paused.
  • SIGCONT resumes a paused entity.
  • SIGHUP, SIGTERM, and SIGUSR are delivered to ctx.onSignal(...) while the current wake is active.
  • SIGTERM transitions idle or paused entities to stopped; running entities move to stopping until runtime cleanup finishes.
  • SIGKILL immediately moves the entity to killed, unregisters wakes, and closes the entity streams.

Built-in agents

The previous registerChatAgent / registerResearcherAgent / registerCoderAgent / registerOracleAgent helpers have been removed. Built-in agents now live in @electric-ax/agents (Horton + worker). To register them in your own runtime, use createBuiltinAgentHandler from @electric-ax/agents.

Timeline Helpers

The runtime also exports timeline helpers used by the UI layer:

  • createEntityIncludesQuery(db)
  • getEntityState(runs, inbox)
  • buildSections(runs, inbox)
  • timelineToMessages(db)

useChat(db) uses the query-based IncludesRun / IncludesInboxMessage arrays, and timelineToMessages(db) builds LLM messages from the same shaped data.

Testing

The old public createTestAdapter API has been removed.

For integration tests inside this repo, see:

License

Apache-2.0

版本与发布记录

@electric-ax/agents-runtime 共发布 29 个版本,首次发布于 2026/04/23;过去 12 个月发布了 29 个版本。最新版本附带 npm provenance 构建来源证明,可追溯到公开的 CI 构建记录。

版本发布日期距今
0.6.3latest2026/06/264 个月前
0.6.22026/06/234 个月前
0.6.12026/06/174 个月前
0.6.02026/06/164 个月前
0.4.12026/06/164 个月前
0.4.02026/06/154 个月前

关于 @electric-ax/agents-runtime 的常见问题

@electric-ax/agents-runtime 是什么?

Electric agent runtime — behavioral stack for agent entities over durable streams

如何安装 @electric-ax/agents-runtime?

在项目目录运行 npm install @electric-ax/agents-runtime,或使用 pnpm add / yarn add / bun add。

@electric-ax/agents-runtime 支持 TypeScript 吗?

支持。包内自带 TypeScript 类型声明(package.json 声明了 types 入口),安装后即可获得类型提示,无需再安装 @types 包。

@electric-ax/agents-runtime 支持 ESM 还是 CommonJS?

同时提供 ES Module 与 CommonJS 入口,import 和 require() 都可以使用,打包工具会自动选择合适的版本。

@electric-ax/agents-runtime 可以商用吗?

@electric-ax/agents-runtime 使用 Apache-2.0 许可。宽松许可:允许商业使用、修改和再分发,并附带明确的专利授权;分发时需保留许可与 NOTICE 文件,修改过的文件要注明改动。以上为概括,不构成法律意见,请以许可原文为准。

@electric-ax/agents-runtime 的下载量有多少?

根据 npm 官方下载统计,@electric-ax/agents-runtime 在 2026/10/14 – 2026/11/12 这 30 天内被下载 11,318 次,日均约 377 次。下载量会把 CI 构建、镜像同步和作为其他包依赖的安装一并计入,适合横向比较热度,不能直接等同于使用人数。

@electric-ax/agents-runtime 还在维护吗?

最新版本 0.6.3 发布于 2026/06/26(4 个月前);至今共发布 29 个版本,其中过去 12 个月发布了 29 个;首次发布于 2026/04/23。发布频率只是参考信号:功能稳定的工具可能很少需要更新,建议结合源码仓库的 issue 和提交记录判断。