@electric-ax/agents 0.2.2 → 0.2.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/entrypoint.js +40 -12
- package/dist/index.cjs +40 -12
- package/dist/index.js +40 -12
- package/docs/entities/agents/coder.md +99 -0
- package/docs/entities/agents/horton.md +16 -13
- package/docs/entities/agents/worker.md +18 -18
- package/docs/entities/patterns/blackboard.md +6 -6
- package/docs/entities/patterns/dispatcher.md +1 -1
- package/docs/entities/patterns/manager-worker.md +1 -1
- package/docs/entities/patterns/map-reduce.md +1 -1
- package/docs/entities/patterns/pipeline.md +1 -1
- package/docs/entities/patterns/reactive-observers.md +2 -2
- package/docs/examples/playground.md +42 -26
- package/docs/index.md +23 -23
- package/docs/quickstart.md +13 -13
- package/docs/reference/agent-config.md +20 -12
- package/docs/reference/agent-tool.md +1 -1
- package/docs/reference/built-in-collections.md +21 -21
- package/docs/reference/cli.md +39 -30
- package/docs/reference/entity-definition.md +9 -9
- package/docs/reference/entity-handle.md +2 -2
- package/docs/reference/entity-registry.md +1 -1
- package/docs/reference/handler-context.md +69 -18
- package/docs/reference/runtime-handler.md +25 -23
- package/docs/reference/shared-state-handle.md +7 -7
- package/docs/reference/state-collection-proxy.md +1 -1
- package/docs/reference/wake-event.md +23 -23
- package/docs/usage/app-setup.md +24 -23
- package/docs/usage/clients-and-react.md +44 -36
- package/docs/usage/configuring-the-agent.md +25 -19
- package/docs/usage/context-composition.md +12 -12
- package/docs/usage/defining-entities.md +36 -36
- package/docs/usage/defining-tools.md +45 -45
- package/docs/usage/embedded-builtins.md +48 -47
- package/docs/usage/managing-state.md +12 -12
- package/docs/usage/overview.md +52 -45
- package/docs/usage/programmatic-runtime-client.md +50 -47
- package/docs/usage/shared-state.md +32 -32
- package/docs/usage/spawning-and-coordinating.md +9 -9
- package/docs/usage/testing.md +14 -14
- package/docs/usage/waking-entities.md +13 -13
- package/docs/usage/writing-handlers.md +57 -26
- package/package.json +5 -2
- package/scripts/sync-docs.mjs +42 -0
- package/skills/quickstart/scaffold/package.json +1 -0
- package/skills/quickstart/scaffold-ui/index.html +1 -1
- package/skills/quickstart/scaffold-ui/main.tsx +221 -16
- package/skills/quickstart.md +49 -94
- package/docs/examples/mega-draw.md +0 -106
package/dist/entrypoint.js
CHANGED
|
@@ -48,6 +48,10 @@ function formatArgs(args) {
|
|
|
48
48
|
};
|
|
49
49
|
}
|
|
50
50
|
const serverLog = {
|
|
51
|
+
debug(...args) {
|
|
52
|
+
const { msg } = formatArgs(args);
|
|
53
|
+
logger.debug(msg);
|
|
54
|
+
},
|
|
51
55
|
info(...args) {
|
|
52
56
|
const { msg } = formatArgs(args);
|
|
53
57
|
logger.info(msg);
|
|
@@ -792,15 +796,38 @@ function findLatestQuestion(items) {
|
|
|
792
796
|
return void 0;
|
|
793
797
|
}
|
|
794
798
|
function resolveDocsRoot(workingDirectory) {
|
|
799
|
+
const envDocsRoot = process.env.HORTON_DOCS_ROOT;
|
|
795
800
|
const candidates = [
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
801
|
+
envDocsRoot ? {
|
|
802
|
+
path: envDocsRoot,
|
|
803
|
+
requireIndex: false
|
|
804
|
+
} : null,
|
|
805
|
+
{
|
|
806
|
+
path: path.resolve(workingDirectory, `electric-agents-docs/docs`),
|
|
807
|
+
requireIndex: false
|
|
808
|
+
},
|
|
809
|
+
{
|
|
810
|
+
path: path.resolve(process.cwd(), `electric-agents-docs/docs`),
|
|
811
|
+
requireIndex: false
|
|
812
|
+
},
|
|
813
|
+
{
|
|
814
|
+
path: path.resolve(MODULE_DIR, `../docs`),
|
|
815
|
+
requireIndex: true
|
|
816
|
+
},
|
|
817
|
+
{
|
|
818
|
+
path: path.resolve(MODULE_DIR, `../../docs`),
|
|
819
|
+
requireIndex: true
|
|
820
|
+
},
|
|
821
|
+
{
|
|
822
|
+
path: path.resolve(MODULE_DIR, `../../../../website/docs/agents`),
|
|
823
|
+
requireIndex: true
|
|
824
|
+
},
|
|
825
|
+
{
|
|
826
|
+
path: path.resolve(MODULE_DIR, `../../../../../electric-agents-docs/docs`),
|
|
827
|
+
requireIndex: false
|
|
828
|
+
}
|
|
829
|
+
].filter((value) => Boolean(value));
|
|
830
|
+
for (const candidate of candidates) if (fs.existsSync(candidate.path) && (!candidate.requireIndex || fs.existsSync(path.join(candidate.path, `index.md`)))) return candidate.path;
|
|
804
831
|
return null;
|
|
805
832
|
}
|
|
806
833
|
var DocsKnowledgeBase = class {
|
|
@@ -830,7 +857,7 @@ var DocsKnowledgeBase = class {
|
|
|
830
857
|
return db$1;
|
|
831
858
|
} catch (error) {
|
|
832
859
|
const message = error instanceof Error ? error.message : String(error);
|
|
833
|
-
|
|
860
|
+
serverLog.debug(`${this.logPrefix} falling back to in-memory docs index: ${message}`);
|
|
834
861
|
return null;
|
|
835
862
|
}
|
|
836
863
|
}
|
|
@@ -917,7 +944,7 @@ var DocsKnowledgeBase = class {
|
|
|
917
944
|
}
|
|
918
945
|
this.fallbackFingerprint = fingerprint;
|
|
919
946
|
const stats$1 = this.stats();
|
|
920
|
-
|
|
947
|
+
serverLog.debug(`${this.logPrefix} indexed ${stats$1.docCount} docs into ${stats$1.chunkCount} chunks (${stats$1.fingerprint.slice(0, 12)}...)`);
|
|
921
948
|
return stats$1;
|
|
922
949
|
}
|
|
923
950
|
const db$1 = this.db;
|
|
@@ -954,7 +981,7 @@ var DocsKnowledgeBase = class {
|
|
|
954
981
|
});
|
|
955
982
|
reset();
|
|
956
983
|
const stats = this.stats();
|
|
957
|
-
|
|
984
|
+
serverLog.debug(`${this.logPrefix} indexed ${stats.docCount} docs into ${stats.chunkCount} chunks (${stats.fingerprint.slice(0, 12)}...)`);
|
|
958
985
|
return stats;
|
|
959
986
|
}
|
|
960
987
|
hybridSearch(query, limit = DEFAULT_K) {
|
|
@@ -1094,7 +1121,7 @@ function renderSearchResults(query, results, docsRoot) {
|
|
|
1094
1121
|
return lines.join(`\n`);
|
|
1095
1122
|
}
|
|
1096
1123
|
function logSearchResults(kind, query, output) {
|
|
1097
|
-
|
|
1124
|
+
serverLog.debug(`[horton-docs] ${kind} search for "${query}"\n${output}\n`);
|
|
1098
1125
|
}
|
|
1099
1126
|
function createHortonDocsSupport(workingDirectory, opts = {}) {
|
|
1100
1127
|
const docsRoot = opts.docsRoot ?? resolveDocsRoot(workingDirectory);
|
|
@@ -2267,6 +2294,7 @@ async function saveCache(cachePath, catalog, cacheDir) {
|
|
|
2267
2294
|
const obj = {};
|
|
2268
2295
|
for (const [name, meta] of catalog) obj[name] = meta;
|
|
2269
2296
|
fs.mkdirSync(cacheDir, { recursive: true });
|
|
2297
|
+
await fs$1.writeFile(path.join(cacheDir, `.gitignore`), `*\n`, `utf-8`);
|
|
2270
2298
|
await fs$1.writeFile(cachePath, JSON.stringify(obj, null, 2), `utf-8`);
|
|
2271
2299
|
}
|
|
2272
2300
|
function sha256(content) {
|
package/dist/index.cjs
CHANGED
|
@@ -71,6 +71,10 @@ function formatArgs(args) {
|
|
|
71
71
|
};
|
|
72
72
|
}
|
|
73
73
|
const serverLog = {
|
|
74
|
+
debug(...args) {
|
|
75
|
+
const { msg } = formatArgs(args);
|
|
76
|
+
logger.debug(msg);
|
|
77
|
+
},
|
|
74
78
|
info(...args) {
|
|
75
79
|
const { msg } = formatArgs(args);
|
|
76
80
|
logger.info(msg);
|
|
@@ -815,15 +819,38 @@ function findLatestQuestion(items) {
|
|
|
815
819
|
return void 0;
|
|
816
820
|
}
|
|
817
821
|
function resolveDocsRoot(workingDirectory) {
|
|
822
|
+
const envDocsRoot = process.env.HORTON_DOCS_ROOT;
|
|
818
823
|
const candidates = [
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
|
|
824
|
+
envDocsRoot ? {
|
|
825
|
+
path: envDocsRoot,
|
|
826
|
+
requireIndex: false
|
|
827
|
+
} : null,
|
|
828
|
+
{
|
|
829
|
+
path: node_path.default.resolve(workingDirectory, `electric-agents-docs/docs`),
|
|
830
|
+
requireIndex: false
|
|
831
|
+
},
|
|
832
|
+
{
|
|
833
|
+
path: node_path.default.resolve(process.cwd(), `electric-agents-docs/docs`),
|
|
834
|
+
requireIndex: false
|
|
835
|
+
},
|
|
836
|
+
{
|
|
837
|
+
path: node_path.default.resolve(MODULE_DIR, `../docs`),
|
|
838
|
+
requireIndex: true
|
|
839
|
+
},
|
|
840
|
+
{
|
|
841
|
+
path: node_path.default.resolve(MODULE_DIR, `../../docs`),
|
|
842
|
+
requireIndex: true
|
|
843
|
+
},
|
|
844
|
+
{
|
|
845
|
+
path: node_path.default.resolve(MODULE_DIR, `../../../../website/docs/agents`),
|
|
846
|
+
requireIndex: true
|
|
847
|
+
},
|
|
848
|
+
{
|
|
849
|
+
path: node_path.default.resolve(MODULE_DIR, `../../../../../electric-agents-docs/docs`),
|
|
850
|
+
requireIndex: false
|
|
851
|
+
}
|
|
852
|
+
].filter((value) => Boolean(value));
|
|
853
|
+
for (const candidate of candidates) if (node_fs.default.existsSync(candidate.path) && (!candidate.requireIndex || node_fs.default.existsSync(node_path.default.join(candidate.path, `index.md`)))) return candidate.path;
|
|
827
854
|
return null;
|
|
828
855
|
}
|
|
829
856
|
var DocsKnowledgeBase = class {
|
|
@@ -853,7 +880,7 @@ var DocsKnowledgeBase = class {
|
|
|
853
880
|
return db$1;
|
|
854
881
|
} catch (error) {
|
|
855
882
|
const message = error instanceof Error ? error.message : String(error);
|
|
856
|
-
|
|
883
|
+
serverLog.debug(`${this.logPrefix} falling back to in-memory docs index: ${message}`);
|
|
857
884
|
return null;
|
|
858
885
|
}
|
|
859
886
|
}
|
|
@@ -940,7 +967,7 @@ var DocsKnowledgeBase = class {
|
|
|
940
967
|
}
|
|
941
968
|
this.fallbackFingerprint = fingerprint;
|
|
942
969
|
const stats$1 = this.stats();
|
|
943
|
-
|
|
970
|
+
serverLog.debug(`${this.logPrefix} indexed ${stats$1.docCount} docs into ${stats$1.chunkCount} chunks (${stats$1.fingerprint.slice(0, 12)}...)`);
|
|
944
971
|
return stats$1;
|
|
945
972
|
}
|
|
946
973
|
const db$1 = this.db;
|
|
@@ -977,7 +1004,7 @@ var DocsKnowledgeBase = class {
|
|
|
977
1004
|
});
|
|
978
1005
|
reset();
|
|
979
1006
|
const stats = this.stats();
|
|
980
|
-
|
|
1007
|
+
serverLog.debug(`${this.logPrefix} indexed ${stats.docCount} docs into ${stats.chunkCount} chunks (${stats.fingerprint.slice(0, 12)}...)`);
|
|
981
1008
|
return stats;
|
|
982
1009
|
}
|
|
983
1010
|
hybridSearch(query, limit = DEFAULT_K) {
|
|
@@ -1117,7 +1144,7 @@ function renderSearchResults(query, results, docsRoot) {
|
|
|
1117
1144
|
return lines.join(`\n`);
|
|
1118
1145
|
}
|
|
1119
1146
|
function logSearchResults(kind, query, output) {
|
|
1120
|
-
|
|
1147
|
+
serverLog.debug(`[horton-docs] ${kind} search for "${query}"\n${output}\n`);
|
|
1121
1148
|
}
|
|
1122
1149
|
function createHortonDocsSupport(workingDirectory, opts = {}) {
|
|
1123
1150
|
const docsRoot = opts.docsRoot ?? resolveDocsRoot(workingDirectory);
|
|
@@ -2290,6 +2317,7 @@ async function saveCache(cachePath, catalog, cacheDir) {
|
|
|
2290
2317
|
const obj = {};
|
|
2291
2318
|
for (const [name, meta] of catalog) obj[name] = meta;
|
|
2292
2319
|
node_fs.default.mkdirSync(cacheDir, { recursive: true });
|
|
2320
|
+
await node_fs_promises.default.writeFile(node_path.default.join(cacheDir, `.gitignore`), `*\n`, `utf-8`);
|
|
2293
2321
|
await node_fs_promises.default.writeFile(cachePath, JSON.stringify(obj, null, 2), `utf-8`);
|
|
2294
2322
|
}
|
|
2295
2323
|
function sha256(content) {
|
package/dist/index.js
CHANGED
|
@@ -47,6 +47,10 @@ function formatArgs(args) {
|
|
|
47
47
|
};
|
|
48
48
|
}
|
|
49
49
|
const serverLog = {
|
|
50
|
+
debug(...args) {
|
|
51
|
+
const { msg } = formatArgs(args);
|
|
52
|
+
logger.debug(msg);
|
|
53
|
+
},
|
|
50
54
|
info(...args) {
|
|
51
55
|
const { msg } = formatArgs(args);
|
|
52
56
|
logger.info(msg);
|
|
@@ -791,15 +795,38 @@ function findLatestQuestion(items) {
|
|
|
791
795
|
return void 0;
|
|
792
796
|
}
|
|
793
797
|
function resolveDocsRoot(workingDirectory) {
|
|
798
|
+
const envDocsRoot = process.env.HORTON_DOCS_ROOT;
|
|
794
799
|
const candidates = [
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
800
|
+
envDocsRoot ? {
|
|
801
|
+
path: envDocsRoot,
|
|
802
|
+
requireIndex: false
|
|
803
|
+
} : null,
|
|
804
|
+
{
|
|
805
|
+
path: path.resolve(workingDirectory, `electric-agents-docs/docs`),
|
|
806
|
+
requireIndex: false
|
|
807
|
+
},
|
|
808
|
+
{
|
|
809
|
+
path: path.resolve(process.cwd(), `electric-agents-docs/docs`),
|
|
810
|
+
requireIndex: false
|
|
811
|
+
},
|
|
812
|
+
{
|
|
813
|
+
path: path.resolve(MODULE_DIR, `../docs`),
|
|
814
|
+
requireIndex: true
|
|
815
|
+
},
|
|
816
|
+
{
|
|
817
|
+
path: path.resolve(MODULE_DIR, `../../docs`),
|
|
818
|
+
requireIndex: true
|
|
819
|
+
},
|
|
820
|
+
{
|
|
821
|
+
path: path.resolve(MODULE_DIR, `../../../../website/docs/agents`),
|
|
822
|
+
requireIndex: true
|
|
823
|
+
},
|
|
824
|
+
{
|
|
825
|
+
path: path.resolve(MODULE_DIR, `../../../../../electric-agents-docs/docs`),
|
|
826
|
+
requireIndex: false
|
|
827
|
+
}
|
|
828
|
+
].filter((value) => Boolean(value));
|
|
829
|
+
for (const candidate of candidates) if (fsSync.existsSync(candidate.path) && (!candidate.requireIndex || fsSync.existsSync(path.join(candidate.path, `index.md`)))) return candidate.path;
|
|
803
830
|
return null;
|
|
804
831
|
}
|
|
805
832
|
var DocsKnowledgeBase = class {
|
|
@@ -829,7 +856,7 @@ var DocsKnowledgeBase = class {
|
|
|
829
856
|
return db$1;
|
|
830
857
|
} catch (error) {
|
|
831
858
|
const message = error instanceof Error ? error.message : String(error);
|
|
832
|
-
|
|
859
|
+
serverLog.debug(`${this.logPrefix} falling back to in-memory docs index: ${message}`);
|
|
833
860
|
return null;
|
|
834
861
|
}
|
|
835
862
|
}
|
|
@@ -916,7 +943,7 @@ var DocsKnowledgeBase = class {
|
|
|
916
943
|
}
|
|
917
944
|
this.fallbackFingerprint = fingerprint;
|
|
918
945
|
const stats$1 = this.stats();
|
|
919
|
-
|
|
946
|
+
serverLog.debug(`${this.logPrefix} indexed ${stats$1.docCount} docs into ${stats$1.chunkCount} chunks (${stats$1.fingerprint.slice(0, 12)}...)`);
|
|
920
947
|
return stats$1;
|
|
921
948
|
}
|
|
922
949
|
const db$1 = this.db;
|
|
@@ -953,7 +980,7 @@ var DocsKnowledgeBase = class {
|
|
|
953
980
|
});
|
|
954
981
|
reset();
|
|
955
982
|
const stats = this.stats();
|
|
956
|
-
|
|
983
|
+
serverLog.debug(`${this.logPrefix} indexed ${stats.docCount} docs into ${stats.chunkCount} chunks (${stats.fingerprint.slice(0, 12)}...)`);
|
|
957
984
|
return stats;
|
|
958
985
|
}
|
|
959
986
|
hybridSearch(query, limit = DEFAULT_K) {
|
|
@@ -1093,7 +1120,7 @@ function renderSearchResults(query, results, docsRoot) {
|
|
|
1093
1120
|
return lines.join(`\n`);
|
|
1094
1121
|
}
|
|
1095
1122
|
function logSearchResults(kind, query, output) {
|
|
1096
|
-
|
|
1123
|
+
serverLog.debug(`[horton-docs] ${kind} search for "${query}"\n${output}\n`);
|
|
1097
1124
|
}
|
|
1098
1125
|
function createHortonDocsSupport(workingDirectory, opts = {}) {
|
|
1099
1126
|
const docsRoot = opts.docsRoot ?? resolveDocsRoot(workingDirectory);
|
|
@@ -2266,6 +2293,7 @@ async function saveCache(cachePath, catalog, cacheDir) {
|
|
|
2266
2293
|
const obj = {};
|
|
2267
2294
|
for (const [name, meta] of catalog) obj[name] = meta;
|
|
2268
2295
|
fsSync.mkdirSync(cacheDir, { recursive: true });
|
|
2296
|
+
await fs.writeFile(path.join(cacheDir, `.gitignore`), `*\n`, `utf-8`);
|
|
2269
2297
|
await fs.writeFile(cachePath, JSON.stringify(obj, null, 2), `utf-8`);
|
|
2270
2298
|
}
|
|
2271
2299
|
function sha256(content) {
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Coder
|
|
3
|
+
titleTemplate: "... - Electric Agents"
|
|
4
|
+
description: >-
|
|
5
|
+
Built-in coding-session entity backed by Claude Code or Codex CLI.
|
|
6
|
+
outline: [2, 3]
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
# Coder
|
|
10
|
+
|
|
11
|
+
`coder` is the built-in coding-session entity. It runs a Claude Code or Codex CLI session in a working directory, mirrors the normalized session event stream into entity state, and can be prompted repeatedly across many turns.
|
|
12
|
+
|
|
13
|
+
**Source:** [`packages/agents/src/agents/coding-session.ts`](https://github.com/electric-sql/electric/blob/main/packages/agents/src/agents/coding-session.ts)
|
|
14
|
+
|
|
15
|
+
## Spawn args
|
|
16
|
+
|
|
17
|
+
```ts
|
|
18
|
+
interface CoderArgs {
|
|
19
|
+
agent: "claude" | "codex"
|
|
20
|
+
cwd?: string
|
|
21
|
+
nativeSessionId?: string
|
|
22
|
+
importFrom?: { agent: "claude" | "codex"; sessionId: string }
|
|
23
|
+
}
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
| Field | Required | Description |
|
|
27
|
+
| ----------------- | -------- | ----------- |
|
|
28
|
+
| `agent` | Yes | CLI backend to run: `"claude"` or `"codex"`. |
|
|
29
|
+
| `cwd` | No | Working directory for the CLI. Defaults to the built-in runtime working directory. |
|
|
30
|
+
| `nativeSessionId` | No | Attach to an existing local Claude/Codex session. |
|
|
31
|
+
| `importFrom` | No | Import an existing local session into a new session for the selected backend. |
|
|
32
|
+
|
|
33
|
+
The built-in runtime registers `coder` during bootstrap. Handler code can also call `registerCodingSession(registry, { defaultWorkingDirectory, cliRunner? })` from `@electric-ax/agents`.
|
|
34
|
+
|
|
35
|
+
## Prompt messages
|
|
36
|
+
|
|
37
|
+
The preferred inbox message type is `prompt` with a payload shaped like:
|
|
38
|
+
|
|
39
|
+
```ts
|
|
40
|
+
interface PromptMessage {
|
|
41
|
+
text: string
|
|
42
|
+
}
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
Generic messages with the same `{ text }` payload are also processed, so the dashboard and CLI can send prompts without a custom message type.
|
|
46
|
+
|
|
47
|
+
## State collections
|
|
48
|
+
|
|
49
|
+
`coder` adds three custom state collections:
|
|
50
|
+
|
|
51
|
+
| Collection | Event type | Description |
|
|
52
|
+
| --------------- | ----------------------- | ----------- |
|
|
53
|
+
| `sessionMeta` | `coding_session_meta` | Current session metadata: selected backend, cwd, status, native session id, and errors. |
|
|
54
|
+
| `cursorState` | `coding_session_cursor` | Serialized tail cursor and the last processed inbox key. |
|
|
55
|
+
| `events` | `coding_session_event` | Normalized `agent-session-protocol` events mirrored from the CLI session. |
|
|
56
|
+
|
|
57
|
+
## Handler behavior
|
|
58
|
+
|
|
59
|
+
1. Initializes session metadata and cursor state if needed.
|
|
60
|
+
2. Mirrors existing local session history when attaching or importing.
|
|
61
|
+
3. Processes pending prompt messages in inbox order.
|
|
62
|
+
4. Calls `ctx.recordRun()` around each CLI invocation so parents observing with `wake: "runFinished"` are notified.
|
|
63
|
+
5. Mirrors new CLI events into the `events` collection and appends assistant text as the run response.
|
|
64
|
+
6. Updates `sessionMeta.status` to `idle` or `error`.
|
|
65
|
+
|
|
66
|
+
## Handler API
|
|
67
|
+
|
|
68
|
+
Inside another entity handler, use `ctx.useCodingAgent()` to spawn or attach to a coder:
|
|
69
|
+
|
|
70
|
+
```ts
|
|
71
|
+
const coder = await ctx.useCodingAgent("feature-work", {
|
|
72
|
+
agent: "claude",
|
|
73
|
+
cwd: process.cwd(),
|
|
74
|
+
})
|
|
75
|
+
|
|
76
|
+
coder.send("Implement the requested feature and run the tests.")
|
|
77
|
+
await coder.run
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
`useCodingAgent()` returns a `CodingSessionHandle` with `entityUrl`, `status()`, `meta()`, `send(prompt)`, `run`, `events`, and `messages`.
|
|
81
|
+
|
|
82
|
+
## Horton tools
|
|
83
|
+
|
|
84
|
+
Horton usually interacts with coders through:
|
|
85
|
+
|
|
86
|
+
| Tool | Purpose |
|
|
87
|
+
| -------------- | ------- |
|
|
88
|
+
| `spawn_coder` | Creates a new long-lived `coder`, sends the first prompt, and wakes Horton when the reply lands. |
|
|
89
|
+
| `prompt_coder` | Sends a follow-up prompt to an existing coder URL. |
|
|
90
|
+
|
|
91
|
+
## Details
|
|
92
|
+
|
|
93
|
+
| Property | Value |
|
|
94
|
+
| ----------------- | ----- |
|
|
95
|
+
| Type name | `coder` |
|
|
96
|
+
| Backends | Claude Code and Codex CLI |
|
|
97
|
+
| State | `sessionMeta`, `cursorState`, `events` |
|
|
98
|
+
| Wake support | Uses `ctx.recordRun()` so `runFinished` observers work |
|
|
99
|
+
| Working directory | From spawn args or `registerCodingSession` default |
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
---
|
|
2
2
|
title: Horton agent
|
|
3
|
-
titleTemplate:
|
|
3
|
+
titleTemplate: "... - Electric Agents"
|
|
4
4
|
description: >-
|
|
5
5
|
The built-in Horton assistant - chat, research, code, and dispatch subagents in one entity type.
|
|
6
6
|
outline: [2, 3]
|
|
@@ -8,7 +8,7 @@ outline: [2, 3]
|
|
|
8
8
|
|
|
9
9
|
# Horton agent
|
|
10
10
|
|
|
11
|
-
The built-in assistant registered by the Electric Agents dev server. Horton can chat conversationally, search the web, read and edit files, run shell commands,
|
|
11
|
+
The built-in assistant registered by the Electric Agents dev server. Horton can chat conversationally, search the web, read and edit files, run shell commands, dispatch workers for isolated subtasks, and spawn long-lived coders for code changes.
|
|
12
12
|
|
|
13
13
|
**Source:** [`packages/agents/src/agents/horton.ts`](https://github.com/electric-sql/electric/blob/main/packages/agents/src/agents/horton.ts)
|
|
14
14
|
|
|
@@ -35,6 +35,8 @@ Horton is configured with `ctx.electricTools` plus the base Horton tool set:
|
|
|
35
35
|
| `brave_search` | Web search via the Brave Search API. |
|
|
36
36
|
| `fetch_url` | Fetch a URL and return it as markdown. |
|
|
37
37
|
| `spawn_worker` | Dispatch a subagent for an isolated subtask. |
|
|
38
|
+
| `spawn_coder` | Spawn a long-lived `coder` entity backed by Claude Code or Codex. |
|
|
39
|
+
| `prompt_coder` | Send a follow-up prompt to an existing coder. |
|
|
38
40
|
|
|
39
41
|
`brave_search` requires `BRAVE_SEARCH_API_KEY` in the environment; without it the tool errors at call time.
|
|
40
42
|
|
|
@@ -46,14 +48,14 @@ After the first agent run completes, Horton calls `generateTitle()` (Haiku) to s
|
|
|
46
48
|
|
|
47
49
|
## Details
|
|
48
50
|
|
|
49
|
-
| Property | Value
|
|
50
|
-
| ----------------- |
|
|
51
|
-
| Type name | `horton`
|
|
52
|
-
| Model | `HORTON_MODEL` (`claude-sonnet-4-5-20250929`)
|
|
53
|
-
| Title model | `claude-haiku-4-5-20251001`
|
|
54
|
-
| Tools | `ctx.electricTools` + base Horton tool set, plus docs/skill tools when configured |
|
|
55
|
-
| Working directory | Passed at bootstrap (defaults to `process.cwd()`)
|
|
56
|
-
| Title generation | Yes, after the first run if no title tag exists
|
|
51
|
+
| Property | Value |
|
|
52
|
+
| ----------------- | ------------------------------------------------- |
|
|
53
|
+
| Type name | `horton` |
|
|
54
|
+
| Model | `HORTON_MODEL` (`claude-sonnet-4-5-20250929`) |
|
|
55
|
+
| Title model | `claude-haiku-4-5-20251001` |
|
|
56
|
+
| Tools | `ctx.electricTools` + base Horton tool set, coder tools, plus docs/skill tools when configured |
|
|
57
|
+
| Working directory | Passed at bootstrap (defaults to `process.cwd()`) |
|
|
58
|
+
| Title generation | Yes, after the first run if no title tag exists |
|
|
57
59
|
|
|
58
60
|
## Extending Horton
|
|
59
61
|
|
|
@@ -64,10 +66,10 @@ import {
|
|
|
64
66
|
HORTON_MODEL,
|
|
65
67
|
buildHortonSystemPrompt,
|
|
66
68
|
createHortonTools,
|
|
67
|
-
} from
|
|
69
|
+
} from "@electric-ax/agents"
|
|
68
70
|
|
|
69
|
-
registry.define(
|
|
70
|
-
description:
|
|
71
|
+
registry.define("my-assistant", {
|
|
72
|
+
description: "Horton with an extra custom tool",
|
|
71
73
|
async handler(ctx) {
|
|
72
74
|
const readSet = new Set<string>()
|
|
73
75
|
ctx.useAgent({
|
|
@@ -87,3 +89,4 @@ registry.define('my-assistant', {
|
|
|
87
89
|
## Related
|
|
88
90
|
|
|
89
91
|
- [Worker](./worker) — the subagent type Horton dispatches via `spawn_worker`.
|
|
92
|
+
- [Coder](./coder) — the coding-session entity Horton dispatches via `spawn_coder`.
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
---
|
|
2
2
|
title: Worker
|
|
3
|
-
titleTemplate:
|
|
3
|
+
titleTemplate: "... - Electric Agents"
|
|
4
4
|
description: >-
|
|
5
5
|
Generic sandboxed subagent type. Spawned by Horton (or any agent) via the spawn_worker tool with a system prompt and a chosen tool subset.
|
|
6
6
|
outline: [2, 3]
|
|
@@ -19,7 +19,7 @@ interface WorkerArgs {
|
|
|
19
19
|
systemPrompt: string
|
|
20
20
|
tools?: Array<WorkerToolName>
|
|
21
21
|
sharedDb?: { id: string; schema: SharedStateSchemaMap }
|
|
22
|
-
sharedDbToolMode?:
|
|
22
|
+
sharedDbToolMode?: "full" | "write-only"
|
|
23
23
|
}
|
|
24
24
|
```
|
|
25
25
|
|
|
@@ -36,13 +36,13 @@ interface WorkerArgs {
|
|
|
36
36
|
|
|
37
37
|
```ts
|
|
38
38
|
type WorkerToolName =
|
|
39
|
-
|
|
|
40
|
-
|
|
|
41
|
-
|
|
|
42
|
-
|
|
|
43
|
-
|
|
|
44
|
-
|
|
|
45
|
-
|
|
|
39
|
+
| "bash"
|
|
40
|
+
| "read"
|
|
41
|
+
| "write"
|
|
42
|
+
| "edit"
|
|
43
|
+
| "brave_search"
|
|
44
|
+
| "fetch_url"
|
|
45
|
+
| "spawn_worker"
|
|
46
46
|
```
|
|
47
47
|
|
|
48
48
|
These are the same primitives Horton uses. Pick the smallest subset the worker needs — tools are the worker's permission set.
|
|
@@ -56,9 +56,9 @@ The canonical way to spawn a worker is the `spawn_worker` tool, which Horton cal
|
|
|
56
56
|
```ts
|
|
57
57
|
spawn_worker({
|
|
58
58
|
systemPrompt:
|
|
59
|
-
|
|
60
|
-
tools: [
|
|
61
|
-
initialMessage:
|
|
59
|
+
"You are a focused researcher. Find the three most-cited papers on X and return their titles, authors, and DOIs as a markdown table.",
|
|
60
|
+
tools: ["brave_search", "fetch_url"],
|
|
61
|
+
initialMessage: "Begin research now.",
|
|
62
62
|
})
|
|
63
63
|
```
|
|
64
64
|
|
|
@@ -93,10 +93,10 @@ When you finish, respond with a concise report covering what was done and any ke
|
|
|
93
93
|
|
|
94
94
|
## Details
|
|
95
95
|
|
|
96
|
-
| Property | Value
|
|
97
|
-
| ----------------- |
|
|
98
|
-
| Type name | `worker`
|
|
99
|
-
| Model | `HORTON_MODEL` (`claude-sonnet-4-5-20250929`)
|
|
100
|
-
| Tools | Subset of 7 primitives plus optional shared-state tools. **No `ctx.electricTools`.**
|
|
101
|
-
| Working directory | Provided to `registerWorker` at bootstrap
|
|
96
|
+
| Property | Value |
|
|
97
|
+
| ----------------- | --------------------------------------------------------------------- |
|
|
98
|
+
| Type name | `worker` |
|
|
99
|
+
| Model | `HORTON_MODEL` (`claude-sonnet-4-5-20250929`) |
|
|
100
|
+
| Tools | Subset of 7 primitives plus optional shared-state tools. **No `ctx.electricTools`.** |
|
|
101
|
+
| Working directory | Provided to `registerWorker` at bootstrap |
|
|
102
102
|
| Description | `Internal — generic worker spawned by other agents. Configure via spawn args (systemPrompt + tools + optional sharedDb).` |
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
---
|
|
2
2
|
title: Blackboard (shared state)
|
|
3
|
-
titleTemplate:
|
|
3
|
+
titleTemplate: "... - Electric Agents"
|
|
4
4
|
description: >-
|
|
5
5
|
Multi-agent coordination using shared state as a common data structure for reads and writes.
|
|
6
6
|
outline: [2, 3]
|
|
@@ -23,12 +23,12 @@ const debateSchema = {
|
|
|
23
23
|
arguments: {
|
|
24
24
|
schema: z.object({
|
|
25
25
|
key: z.string(),
|
|
26
|
-
side: z.enum([
|
|
26
|
+
side: z.enum(["pro", "con"]),
|
|
27
27
|
text: z.string(),
|
|
28
28
|
round: z.number(),
|
|
29
29
|
}),
|
|
30
|
-
type:
|
|
31
|
-
primaryKey:
|
|
30
|
+
type: "shared:argument",
|
|
31
|
+
primaryKey: "key",
|
|
32
32
|
},
|
|
33
33
|
}
|
|
34
34
|
```
|
|
@@ -36,7 +36,7 @@ const debateSchema = {
|
|
|
36
36
|
### Registration
|
|
37
37
|
|
|
38
38
|
```ts
|
|
39
|
-
import { db } from
|
|
39
|
+
import { db } from "@electric-ax/agents-runtime"
|
|
40
40
|
|
|
41
41
|
export function registerDebate(registry: EntityRegistry) {
|
|
42
42
|
registry.define(`debate`, {
|
|
@@ -97,7 +97,7 @@ const args = shared.arguments.toArray
|
|
|
97
97
|
### State transitions
|
|
98
98
|
|
|
99
99
|
```ts
|
|
100
|
-
type DebateStatus =
|
|
100
|
+
type DebateStatus = "idle" | "debating" | "ruling" | "done"
|
|
101
101
|
```
|
|
102
102
|
|
|
103
103
|
## Other blackboard variants
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
---
|
|
2
2
|
title: Reactive observers
|
|
3
|
-
titleTemplate:
|
|
3
|
+
titleTemplate: "... - Electric Agents"
|
|
4
4
|
description: >-
|
|
5
5
|
Pattern for entities that watch others and react to changes using ctx.observe() with wake conditions.
|
|
6
6
|
outline: [2, 3]
|
|
@@ -66,7 +66,7 @@ The monitor wraps the base observe tool to also transition its own state to `obs
|
|
|
66
66
|
The `observe_entity` tool lets the LLM decide what to watch:
|
|
67
67
|
|
|
68
68
|
```ts
|
|
69
|
-
import { entity } from
|
|
69
|
+
import { entity } from "@electric-ax/agents-runtime"
|
|
70
70
|
|
|
71
71
|
export function createObserveTool(ctx: HandlerContext): AgentTool {
|
|
72
72
|
return {
|