@ax-llm/ax 20.0.2 → 21.0.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +4 -9
- package/index.cjs +621 -707
- package/index.cjs.map +1 -1
- package/index.d.cts +5657 -5226
- package/index.d.ts +5657 -5226
- package/index.global.js +623 -709
- package/index.global.js.map +1 -1
- package/index.js +621 -707
- package/index.js.map +1 -1
- package/package.json +1 -1
- package/skills/ax-agent-optimize.md +1 -1
- package/skills/ax-agent.md +369 -133
- package/skills/ax-ai.md +22 -3
- package/skills/ax-audio.md +251 -0
- package/skills/ax-flow.md +12 -1
- package/skills/ax-gen.md +3 -2
- package/skills/ax-gepa.md +1 -1
- package/skills/ax-learn.md +1 -1
- package/skills/ax-llm.md +2 -2
- package/skills/ax-signature.md +1 -1
package/skills/ax-agent.md
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: ax-agent
|
|
3
3
|
description: This skill helps an LLM generate correct AxAgent code using @ax-llm/ax. Use when the user asks about agent(), child agents, namespaced functions, discovery mode, shared fields, llmQuery(...), RLM code execution, recursionOptions, or agent runtime behavior. For tuning and eval with agent.optimize(...), use ax-agent-optimize.
|
|
4
|
-
version: "
|
|
4
|
+
version: "21.0.2"
|
|
5
5
|
---
|
|
6
6
|
|
|
7
7
|
# AxAgent Codegen Rules (@ax-llm/ax)
|
|
@@ -21,15 +21,16 @@ Your job is not just to write valid code. Your job is to choose the smallest cor
|
|
|
21
21
|
- Use `agent(...)`, not `new AxAgent(...)`.
|
|
22
22
|
- Prefer `fn(...)` for host-side function definitions instead of hand-writing JSON Schema objects.
|
|
23
23
|
- Prefer namespaced functions such as `utils.search(...)` or `kb.find(...)`.
|
|
24
|
-
-
|
|
24
|
+
- Pass child agents directly in `functions: [...]`. They land under their `agentIdentity.namespace` (or `utils` if unset), exactly like a `fn()` tool.
|
|
25
25
|
- If `functions.discovery` is `true`, discover callables from modules before using them.
|
|
26
26
|
- In stdout-mode RLM, use one observable `console.log(...)` step per non-final actor turn.
|
|
27
27
|
- Prefer `promptLevel: 'default'` for normal use; use `promptLevel: 'detailed'` when you want extra anti-pattern examples and tighter teaching scaffolding in the actor prompt.
|
|
28
28
|
- Default to `contextPolicy: { preset: 'checkpointed', budget: 'balanced' }` for most RLM tasks.
|
|
29
29
|
- Prefer `contextPolicy: { preset: 'adaptive', budget: 'balanced' }` when older successful turns should collapse sooner while live runtime state stays visible.
|
|
30
|
-
- Prefer `
|
|
31
|
-
- Use `
|
|
32
|
-
- Use `agentStatusCallback` when the user wants real-time task progress updates from the actor via `await
|
|
30
|
+
- Prefer `executorModelPolicy` when the actor may need to upgrade after repeated error turns or discovery in specific namespaces without also upgrading the responder.
|
|
31
|
+
- Use `executorTurnCallback` when the user needs per-turn observability into generated code, raw runtime result, formatted output, or provider thoughts.
|
|
32
|
+
- Use `agentStatusCallback` when the user wants real-time task progress updates from the actor via `await reportSuccess(message)` and `await reportFailure(message)` calls.
|
|
33
|
+
- Use `onFunctionCall` when the user wants to observe every function the actor invokes from the JS runtime (their own registered functions plus internal globals like child agents, `discoverModules`, `discoverFunctions`, `consult`).
|
|
33
34
|
|
|
34
35
|
## Decision Guide
|
|
35
36
|
|
|
@@ -38,12 +39,15 @@ Map user intent to agent shape before writing code:
|
|
|
38
39
|
- "Use tools and answer" -> plain `agent(...)` with local functions, no recursion, no extra observability.
|
|
39
40
|
- "Inspect large context with code" -> add `runtime`, `contextFields`, and usually `contextPolicy: { preset: 'checkpointed', budget: 'balanced' }`.
|
|
40
41
|
- "Delegate focused semantic subtasks" -> use `llmQuery(...)`; add `mode: 'advanced'` only when child tasks need their own runtime, tools, or discovery loop.
|
|
41
|
-
- "Need child agents with distinct responsibilities" ->
|
|
42
|
+
- "Need child agents with distinct responsibilities" -> add the child agents to the parent's `functions: [...]` list. Set `agentIdentity.namespace` on each child to control where it lands in the JS runtime (e.g. `team.writer(...)`); otherwise it lands under `utils.<name>` like any other tool.
|
|
42
43
|
- "Need tool discovery because names/schemas are not stable" -> use `functions.discovery: true` and generate discovery-first code.
|
|
43
|
-
- "Need a stronger actor only when the run gets noisy or large" -> use `
|
|
44
|
-
- "Need debugging or traceability" -> start with `debug: true` or `
|
|
45
|
-
- "Need real-time progress updates" -> add `agentStatusCallback` so the actor can call `await
|
|
44
|
+
- "Need a stronger actor only when the run gets noisy or large" -> use `executorModelPolicy` and keep the responder model separate.
|
|
45
|
+
- "Need debugging or traceability" -> start with `debug: true` or `executorTurnCallback`; do not add both unless the user clearly wants both prompt/runtime visibility and structured telemetry.
|
|
46
|
+
- "Need real-time progress updates" -> add `agentStatusCallback` so the actor can call `await reportSuccess(message)` and `await reportFailure(message)` to report sub-task progress.
|
|
47
|
+
- "Need to log/trace every tool call" -> add `onFunctionCall` to receive `{ name, qualifiedName, args, kind }` for each function invoked by the runtime; `kind` is `'external'` for caller-registered functions and `'internal'` for agent-injected ones (child agents, discovery, skills loader).
|
|
46
48
|
- "Need certain errors to escape the agent loop" -> add `bubbleErrors` with an array of error classes; those errors propagate through function handlers, actor code, and llmQuery sub-agents all the way to `.forward()`.
|
|
49
|
+
- "Need to pull relevant memories into context" -> add `onMemoriesSearch` with a vector/BM25 search callback; the distiller and executor gain `await recall(searches)` (returns void; results land on `inputs.memories` next turn) and an `inputs.memories` field. Add `onUsedMemories` if you want to observe what gets loaded.
|
|
50
|
+
- "Need to load skill guides into the executor system prompt on demand" -> add `onSkillsSearch`; the executor gains `await consult(searches)` (returns void; loaded skill bodies render under "Loaded Skills" next turn). Add `onUsedSkills` for observability.
|
|
47
51
|
|
|
48
52
|
Choose options based on user needs, not feature completeness:
|
|
49
53
|
|
|
@@ -53,7 +57,17 @@ Choose options based on user needs, not feature completeness:
|
|
|
53
57
|
|
|
54
58
|
## Mental Model
|
|
55
59
|
|
|
56
|
-
|
|
60
|
+
`AxAgent` is a three-stage pipeline. Each `forward()` call walks (some subset of) the stages in order:
|
|
61
|
+
|
|
62
|
+
```
|
|
63
|
+
distiller (RLM actor) → executor (RLM actor) → responder (synthesizer)
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
- **distiller** always runs first. It sees all original inputs so it can understand and normalize the task; declared `contextFields` stay runtime-only when present. It distils relevant evidence by writing JS code in a multi-turn loop, then calls `final(request, evidence)`. The request becomes the executor's `inputs.executorRequest`; the distiller should expand the original user task with facts found in context, including follow-ups like "yes, do it". When no `contextFields` are configured, it still performs request normalization over the original inputs with `contextFields: []`. **The distiller has no tools** — it only reads, narrows, and forwards. If the user asks for an action (e.g. "run a command"), the distiller forwards it via `final(request, {})`; refusing on the grounds of "no tools" is wrong.
|
|
67
|
+
- **executor** always runs. It receives non-context inputs plus `inputs.executorRequest` and `inputs.distilledContext` from the distiller's `final(request, evidence)` payload. Raw context fields are not present in the executor stage. The executor owns tool use and decides whether to call its available functions or finish directly from the distilled evidence.
|
|
68
|
+
- **responder** always runs last. It synthesizes the user's output signature from whichever upstream actor finished the run.
|
|
69
|
+
|
|
70
|
+
Treat both actor stages (distiller, executor) as long-running JavaScript REPLs that the actor steers over multiple turns, not as fresh script generators on every turn.
|
|
57
71
|
|
|
58
72
|
- Successful code leaves variables, functions, imports, and computed values available in the runtime session.
|
|
59
73
|
- The actor should continue from existing runtime state instead of recreating prior work.
|
|
@@ -90,7 +104,7 @@ Treat these knobs as a bundle:
|
|
|
90
104
|
|
|
91
105
|
- `contextPolicy.preset` decides how much raw history the actor keeps seeing.
|
|
92
106
|
- `promptLevel` decides whether the actor gets just the standard rules or those rules plus detailed anti-pattern examples.
|
|
93
|
-
- `
|
|
107
|
+
- `executorModelPolicy` decides when the actor switches to an override model without changing the responder.
|
|
94
108
|
- Model size decides how well the actor can recover from compressed context and terse guidance.
|
|
95
109
|
|
|
96
110
|
Recommended combinations:
|
|
@@ -99,7 +113,7 @@ Recommended combinations:
|
|
|
99
113
|
- Long multi-turn task, general default, medium-to-strong model: `preset: 'checkpointed', budget: 'balanced'`.
|
|
100
114
|
- Long task where you want older successful work summarized sooner: `preset: 'adaptive', budget: 'balanced'`.
|
|
101
115
|
- Very long task under token pressure, stronger model only: `preset: 'lean'`.
|
|
102
|
-
- Discovery-heavy work with a cheaper default actor: keep the responder cheap and add `
|
|
116
|
+
- Discovery-heavy work with a cheaper default actor: keep the responder cheap and add `executorModelPolicy` so only the actor upgrades under pressure.
|
|
103
117
|
|
|
104
118
|
Practical rule:
|
|
105
119
|
|
|
@@ -108,13 +122,14 @@ Practical rule:
|
|
|
108
122
|
- `checkpointed + balanced` is the default middle ground for real agent work.
|
|
109
123
|
- `adaptive + balanced` is the proactive-summarization variant when you want older successful work compressed sooner.
|
|
110
124
|
- `lean` should be reserved for models that can reason well from runtime state plus summaries instead of exact old code/output.
|
|
111
|
-
- `
|
|
125
|
+
- `executorModelPolicy` is usually better than globally upgrading the whole agent when the bottleneck is actor exploration rather than responder synthesis.
|
|
112
126
|
|
|
113
127
|
## Critical Rules
|
|
114
128
|
|
|
115
129
|
- Use `agent(...)` factory syntax for new code.
|
|
116
|
-
-
|
|
130
|
+
- Add child agents to the parent's `functions: [...]` list. Each child's `agentIdentity.namespace` (or `utils`, the default) determines the runtime call site, e.g. `await team.writer({...})`.
|
|
117
131
|
- If `functions.discovery` is `true`, call `discoverModules(...)` first, then `discoverFunctions(...)`, then call only discovered functions.
|
|
132
|
+
- The `Javascript Code` output field uses Ax's normal field-pair response shape, but its value must be executable JavaScript only; do not emit plain `task:` / `evidence:` labels, prose, markdown fences, or `<think>` tags as the value.
|
|
118
133
|
- In stdout-mode RLM, non-final turns must emit exactly one `console.log(...)` and stop immediately after it.
|
|
119
134
|
- Never combine `console.log(...)` with `await final(...)` or `await askClarification(...)` in the same actor turn.
|
|
120
135
|
- Inside actor-authored JavaScript, `await final(...)` and `await askClarification(...)` end the current turn immediately; code after them is dead code.
|
|
@@ -156,21 +171,22 @@ const result = await assistant.forward(llm, { query: 'What is TypeScript?' });
|
|
|
156
171
|
console.log(result.answer);
|
|
157
172
|
```
|
|
158
173
|
|
|
159
|
-
## Child Agents
|
|
174
|
+
## Child Agents As Tools
|
|
160
175
|
|
|
161
|
-
|
|
176
|
+
Child agents are passed in the parent's `functions` list — there's no separate `agents` option. Each child agent's `agentIdentity.namespace` (or `utils`, the default) determines where it lands in the JS runtime:
|
|
162
177
|
|
|
163
178
|
```typescript
|
|
164
179
|
const writer = agent('draft:string -> revision:string', {
|
|
165
180
|
agentIdentity: {
|
|
166
181
|
name: 'Writer',
|
|
167
182
|
description: 'Polishes drafts',
|
|
183
|
+
namespace: 'team',
|
|
168
184
|
},
|
|
169
185
|
contextFields: [],
|
|
170
186
|
});
|
|
171
187
|
|
|
172
188
|
const coordinator = agent('query:string -> answer:string', {
|
|
173
|
-
|
|
189
|
+
functions: [writer],
|
|
174
190
|
contextFields: [],
|
|
175
191
|
});
|
|
176
192
|
```
|
|
@@ -178,42 +194,40 @@ const coordinator = agent('query:string -> answer:string', {
|
|
|
178
194
|
Generated runtime call:
|
|
179
195
|
|
|
180
196
|
```javascript
|
|
181
|
-
const result = await
|
|
182
|
-
```
|
|
183
|
-
|
|
184
|
-
Custom child-agent module:
|
|
185
|
-
|
|
186
|
-
```typescript
|
|
187
|
-
const writer = agent('draft:string -> revision:string', {
|
|
188
|
-
agentIdentity: {
|
|
189
|
-
name: 'Writer',
|
|
190
|
-
description: 'Polishes drafts',
|
|
191
|
-
},
|
|
192
|
-
contextFields: [],
|
|
193
|
-
});
|
|
194
|
-
|
|
195
|
-
const coordinator = agent('query:string -> answer:string', {
|
|
196
|
-
agentIdentity: {
|
|
197
|
-
name: 'Coordinator',
|
|
198
|
-
description: 'Routes work',
|
|
199
|
-
namespace: 'team',
|
|
200
|
-
},
|
|
201
|
-
agents: { local: [writer] },
|
|
202
|
-
contextFields: [],
|
|
203
|
-
});
|
|
197
|
+
const result = await team.writer({ draft: '...' });
|
|
204
198
|
```
|
|
205
199
|
|
|
206
|
-
|
|
200
|
+
Without `agentIdentity.namespace`, the child lands under `utils.<name>` like any other tool:
|
|
207
201
|
|
|
208
202
|
```javascript
|
|
209
|
-
const result = await
|
|
203
|
+
const result = await utils.writer({ draft: '...' });
|
|
210
204
|
```
|
|
211
205
|
|
|
212
206
|
Rules:
|
|
213
207
|
|
|
214
|
-
-
|
|
215
|
-
-
|
|
216
|
-
-
|
|
208
|
+
- Add child agents to `functions: [...]` — same array as `fn(...)` tools.
|
|
209
|
+
- Set `agentIdentity.namespace` on the child to control its runtime call site.
|
|
210
|
+
- `onFunctionCall` observers receive `kind: 'internal'` for agent-derived calls (vs. `'external'` for user-registered tools).
|
|
211
|
+
|
|
212
|
+
### Reserved namespace names
|
|
213
|
+
|
|
214
|
+
The agent runtime injects a fixed set of globals into the JS REPL. These names cannot be used as `agentIdentity.namespace` values or as agent-function namespaces — the constructor throws `Agent function namespace "<name>" conflicts with an AxAgent runtime global and is reserved`.
|
|
215
|
+
|
|
216
|
+
```
|
|
217
|
+
inputs // input field bag
|
|
218
|
+
llmQuery // delegated semantic queries
|
|
219
|
+
final // turn-end signal
|
|
220
|
+
askClarification // request user clarification
|
|
221
|
+
reportSuccess // mid-run success ping (when agentStatusCallback set)
|
|
222
|
+
reportFailure // mid-run failure ping (when agentStatusCallback set)
|
|
223
|
+
inspectRuntime // runtime variable snapshot
|
|
224
|
+
discoverModules // module discovery (when functionDiscovery: true)
|
|
225
|
+
discoverFunctions // function discovery (when functionDiscovery: true)
|
|
226
|
+
consult // skill load (when onSkillsSearch set)
|
|
227
|
+
recall // memory load (when onMemoriesSearch set)
|
|
228
|
+
```
|
|
229
|
+
|
|
230
|
+
Pick any other lowercase identifier (`utils`, `kb`, `tools`, `team`, `db`, etc.) — the runtime accepts arbitrary names as long as they don't collide with this list.
|
|
217
231
|
|
|
218
232
|
## Tool Functions And Namespaces
|
|
219
233
|
|
|
@@ -448,7 +462,7 @@ const dbTool = fn('queryUsers')
|
|
|
448
462
|
|
|
449
463
|
const myAgent = agent('query:string -> answer:string', {
|
|
450
464
|
contextFields: [],
|
|
451
|
-
functions:
|
|
465
|
+
functions: [dbTool],
|
|
452
466
|
bubbleErrors: [DatabaseError, AuthError],
|
|
453
467
|
});
|
|
454
468
|
|
|
@@ -521,11 +535,8 @@ const analyst = agent('context:string, query:string -> answer:string', {
|
|
|
521
535
|
namespace: 'team',
|
|
522
536
|
},
|
|
523
537
|
contextFields: ['context'],
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
discovery: true,
|
|
527
|
-
local: tools,
|
|
528
|
-
},
|
|
538
|
+
functions: [writer, ...tools],
|
|
539
|
+
functionDiscovery: true,
|
|
529
540
|
});
|
|
530
541
|
```
|
|
531
542
|
|
|
@@ -612,7 +623,7 @@ Reason: turn 2 reuses `customers` from the persistent runtime. `Live Runtime Sta
|
|
|
612
623
|
|
|
613
624
|
## AxJSRuntime Security
|
|
614
625
|
|
|
615
|
-
Default `new AxJSRuntime()` is hardened: no network, no fs, no child_process, `import()` blocked, intrinsics frozen, `ShadowRealm` locked to `undefined`, worker IPC locked in browser/Deno, and on Node 20+ the OS Permission Model auto-engages (using `--permission` on Node 23.5+ or `--experimental-permission` on Node 20–23.4) as a second defense layer. You do not need to configure anything to get the strict profile — opt in only to the capability the user actually asked for.
|
|
626
|
+
Default `new AxJSRuntime()` is hardened: no network, no fs, no child_process, `import()` blocked, intrinsics frozen, `ShadowRealm` locked to `undefined`, worker IPC locked in browser/Deno/Bun, Bun workers use `smol: true`, and on Node 20+ the OS Permission Model auto-engages (using `--permission` on Node 23.5+ or `--experimental-permission` on Node 20–23.4) as a second defense layer. You do not need to configure anything to get the strict profile — opt in only to the capability the user actually asked for.
|
|
616
627
|
|
|
617
628
|
**Permission enum** (`AxJSRuntimePermission`):
|
|
618
629
|
`NETWORK`, `STORAGE`, `CODE_LOADING`, `COMMUNICATION`, `TIMING`, `WORKERS`, `FILESYSTEM` (new), `CHILD_PROCESS` (new).
|
|
@@ -625,9 +636,9 @@ Default `new AxJSRuntime()` is hardened: no network, no fs, no child_process, `i
|
|
|
625
636
|
| `allowedModules` | `[]` | Whitelist of specifiers permitted when `blockDynamicImport` is on. |
|
|
626
637
|
| `freezeIntrinsics` | `true` | Freezes `Object`/`Array`/`Promise`/etc. prototypes. |
|
|
627
638
|
| `blockShadowRealm` | `true` | Locks `globalThis.ShadowRealm` to `undefined`. |
|
|
628
|
-
| `lockWorkerIPC` | `true` | Locks `self.postMessage`/`onmessage` in browser/Deno workers. |
|
|
639
|
+
| `lockWorkerIPC` | `true` | Locks `self.postMessage`/`onmessage` in browser/Deno/Bun workers. |
|
|
629
640
|
| `preventGlobalThisExtensions` | `false` | Opt-in; breaks top-level `var/let/const` persistence across turns. |
|
|
630
|
-
| `useNodePermissionModel` | `'auto'` | Engages Node Permission Model on Node 20+ (`--permission` on 23.5+, `--experimental-permission` on 20–23.4); skips on older
|
|
641
|
+
| `useNodePermissionModel` | `'auto'` | Engages Node Permission Model on Node 20+ (`--permission` on 23.5+, `--experimental-permission` on 20–23.4); skips on Bun, Deno, browsers, and older Node. |
|
|
631
642
|
| `nodePermissionAllowlist` | `undefined` | Fine-grained `{ fsRead, fsWrite, childProcess, addons, wasi }`. |
|
|
632
643
|
| `resourceLimits` | `undefined` | `{ maxOldGenerationSizeMb, maxYoungGenerationSizeMb, codeRangeSizeMb, stackSizeMb }`. |
|
|
633
644
|
| `allowDenoRemoteImport` | `false` | On Deno, controls whether `NETWORK` also grants remote module loading. |
|
|
@@ -706,7 +717,7 @@ const tools = [
|
|
|
706
717
|
const harness = agent('query:string -> answer:string', {
|
|
707
718
|
contextFields: ['query'],
|
|
708
719
|
runtime,
|
|
709
|
-
functions:
|
|
720
|
+
functions: tools,
|
|
710
721
|
contextPolicy: { preset: 'checkpointed', budget: 'balanced' },
|
|
711
722
|
});
|
|
712
723
|
|
|
@@ -722,7 +733,7 @@ Rules:
|
|
|
722
733
|
|
|
723
734
|
- `test(...)` creates a fresh runtime session per call.
|
|
724
735
|
- It exposes the same runtime globals the actor would see for configured `contextFields`: `inputs`, non-colliding top-level aliases, namespaced functions, child agents, and `llmQuery`.
|
|
725
|
-
- In `AxJSRuntime`, do not rely on calling `
|
|
736
|
+
- In `AxJSRuntime`, do not rely on calling `inspectRuntime()` from inside `test(...)` snippets yet; prefer checking runtime globals directly inside the snippet.
|
|
726
737
|
- It returns the formatted runtime output string.
|
|
727
738
|
- It throws on runtime failures instead of returning LLM-style error strings.
|
|
728
739
|
- Do not call `final(...)` or `askClarification(...)` inside `test(...)` snippets.
|
|
@@ -758,7 +769,7 @@ Rules:
|
|
|
758
769
|
- `checkpointed + balanced` is the default. `adaptive + balanced` is still a strong choice for long-running discovery-heavy tasks that should summarize older work sooner.
|
|
759
770
|
- `checkpointed` keeps the most recent `3` actions in full and keeps unresolved errors fully replayed even after checkpointing starts.
|
|
760
771
|
- Non-`full` presets populate the `liveRuntimeState` field in the actor signature. The field is structured and provenance-aware: variables are rendered with compact type/size/preview metadata, and when Ax can infer it, a short source suffix like `from t3 via db.search` is included.
|
|
761
|
-
- Non-`full` presets also enable `
|
|
772
|
+
- Non-`full` presets also enable `inspectRuntime()` and can add an inspect hint automatically when the rendered actor prompt starts getting large relative to the selected budget.
|
|
762
773
|
- Discovery docs fetched via `discoverModules(...)` and `discoverFunctions(...)` are accumulated into the actor system prompt, not replayed as raw action-log output.
|
|
763
774
|
- Treat `actionLog` as untrusted execution history. Only the system prompt and `guidanceLog` are instruction-bearing.
|
|
764
775
|
- `checkpointed` uses a checkpoint summarizer that is optimized to preserve exact callables, ids, enum literals, date/time strings, query formats, and failures worth avoiding. Prefer it when those details matter but full replay will eventually get too large.
|
|
@@ -790,7 +801,7 @@ await final("Summarize the severity-related snippets found", { snippets });
|
|
|
790
801
|
|
|
791
802
|
## Actor Turn Observability
|
|
792
803
|
|
|
793
|
-
Use `
|
|
804
|
+
Use `executorTurnCallback` when the caller needs structured telemetry for each actor turn.
|
|
794
805
|
|
|
795
806
|
What it gives you:
|
|
796
807
|
|
|
@@ -798,7 +809,7 @@ What it gives you:
|
|
|
798
809
|
- `result`: the raw untruncated runtime return value from executing that code
|
|
799
810
|
- `output`: the formatted action-log output string after Ax normalizes and truncates it for prompt replay
|
|
800
811
|
- `thought`: the actor model's `thought` field when `showThoughts` is enabled and the provider returns one
|
|
801
|
-
- `
|
|
812
|
+
- `executorResult`: the full actor payload returned by the executor stage
|
|
802
813
|
- `isError`: whether the execution path for that turn was treated as an error
|
|
803
814
|
|
|
804
815
|
Use it for:
|
|
@@ -821,7 +832,7 @@ Good pattern:
|
|
|
821
832
|
const supportAgent = agent('query:string -> answer:string', {
|
|
822
833
|
contextFields: ['query'],
|
|
823
834
|
runtime,
|
|
824
|
-
|
|
835
|
+
executorTurnCallback: ({
|
|
825
836
|
turn,
|
|
826
837
|
actionLogEntryCount,
|
|
827
838
|
guidanceLogEntryCount,
|
|
@@ -842,7 +853,7 @@ const supportAgent = agent('query:string -> answer:string', {
|
|
|
842
853
|
thought,
|
|
843
854
|
});
|
|
844
855
|
},
|
|
845
|
-
|
|
856
|
+
executorOptions: {
|
|
846
857
|
model: 'gpt-5.4-mini',
|
|
847
858
|
showThoughts: true,
|
|
848
859
|
},
|
|
@@ -851,7 +862,7 @@ const supportAgent = agent('query:string -> answer:string', {
|
|
|
851
862
|
|
|
852
863
|
## Agent Status Callback
|
|
853
864
|
|
|
854
|
-
Use `agentStatusCallback` when the caller wants real-time progress updates from the actor. When set, the actor can call `await
|
|
865
|
+
Use `agentStatusCallback` when the caller wants real-time progress updates from the actor. When set, the actor can call `await reportSuccess(message)` and `await reportFailure(message)` in its JavaScript turns.
|
|
855
866
|
|
|
856
867
|
```typescript
|
|
857
868
|
const supportAgent = agent('query:string -> answer:string', {
|
|
@@ -866,24 +877,210 @@ const supportAgent = agent('query:string -> answer:string', {
|
|
|
866
877
|
Rules:
|
|
867
878
|
|
|
868
879
|
- `agentStatusCallback` receives `(message: string, status: 'success' | 'failed')`.
|
|
869
|
-
- When set, the actor prompt automatically includes `
|
|
880
|
+
- When set, the actor prompt automatically includes `reportSuccess(message)` and `reportFailure(message)` as available runtime functions.
|
|
870
881
|
- The actor is instructed to keep the user updated of task progress.
|
|
871
|
-
- `
|
|
882
|
+
- `reportSuccess` and `reportFailure` are reserved runtime names when the callback is configured.
|
|
872
883
|
- Child agents inherit the callback via the rlm config.
|
|
873
884
|
|
|
885
|
+
## On Function Call
|
|
886
|
+
|
|
887
|
+
Use `onFunctionCall` when the caller wants to observe every function call the actor makes from the JS runtime. Fires before the underlying function runs.
|
|
888
|
+
|
|
889
|
+
```typescript
|
|
890
|
+
const supportAgent = agent('query:string -> answer:string', {
|
|
891
|
+
contextFields: ['query'],
|
|
892
|
+
runtime,
|
|
893
|
+
functions: [helperAgent, { name: 'lookupOrder', namespace: 'tools', /* ... */ }],
|
|
894
|
+
onFunctionCall: ({ name, qualifiedName, args, kind }) => {
|
|
895
|
+
console.log(`[${kind}] ${qualifiedName}`, args);
|
|
896
|
+
},
|
|
897
|
+
});
|
|
898
|
+
```
|
|
899
|
+
|
|
900
|
+
Rules:
|
|
901
|
+
|
|
902
|
+
- Receives `{ name, qualifiedName, args, kind }` where:
|
|
903
|
+
- `name` is the bare function name (e.g. `'lookupOrder'`).
|
|
904
|
+
- `qualifiedName` is the namespaced name as the actor sees it (e.g. `'tools.lookupOrder'`); for un-namespaced runtime globals it equals `name`.
|
|
905
|
+
- `args` is the resolved positional/named arguments object (`Record<string, unknown>`).
|
|
906
|
+
- `kind` is `'external'` for caller-registered `functions`, `'internal'` for agent-injected globals: child `agents`, `discoverModules`, `discoverFunctions` (when `functionDiscovery: true`), and `consult` (when `onSkillsSearch` is set).
|
|
907
|
+
- Fires once per call, before the function executes. Errors thrown inside the callback are swallowed so they cannot break the actor loop.
|
|
908
|
+
- Independent from the DSP-layer `onFunctionCall` on `AxProgramForwardOptions` — that hook is for LLM tool-calls and never fires under AxAgent (the agent injects functions as runtime globals, not as LLM tools).
|
|
909
|
+
|
|
910
|
+
## Memory Search
|
|
911
|
+
|
|
912
|
+
Use `onMemoriesSearch` when the agent needs to pull task-relevant context — user preferences, prior decisions, project facts, past conversations — from an external store (vector DB, BM25, KV) instead of stuffing everything into the prompt upfront. The actor decides what to load, when, and how much.
|
|
913
|
+
|
|
914
|
+
When `onMemoriesSearch` is set, the distiller and executor stages gain:
|
|
915
|
+
|
|
916
|
+
1. An `inputs.memories` field — an array of `{ id, content }` entries the actor reads directly. Each `content` is opaque markdown (frontmatter, if any, is not parsed).
|
|
917
|
+
2. A `recall(searches: string[]): void` global the actor `await`s to load more entries. Recalled entries are appended to `inputs.memories` and visible from the next turn onward — similar to how `guidance` accumulates. **`recall()` returns nothing**; read `inputs.memories` next turn to see what landed.
|
|
918
|
+
|
|
919
|
+
The responder stage does not receive memories.
|
|
920
|
+
|
|
921
|
+
### Enabling
|
|
922
|
+
|
|
923
|
+
```typescript
|
|
924
|
+
import { agent } from '@ax-llm/ax';
|
|
925
|
+
import type { AxAgentMemoriesSearchFn } from '@ax-llm/ax';
|
|
926
|
+
|
|
927
|
+
// Each result must be { id: string; content: string }
|
|
928
|
+
const onMemoriesSearch: AxAgentMemoriesSearchFn = async (
|
|
929
|
+
searches,
|
|
930
|
+
alreadyLoaded
|
|
931
|
+
) => {
|
|
932
|
+
// `searches` is the full array passed to recall(...) — batch your
|
|
933
|
+
// store lookup in one round-trip.
|
|
934
|
+
// `alreadyLoaded` is the snapshot of `inputs.memories` already in
|
|
935
|
+
// scope. Filter your results so you don't refetch what's already
|
|
936
|
+
// loaded (the runtime dedupes by id, but skipping here saves a
|
|
937
|
+
// round-trip and avoids charging the actor for duplicate tokens).
|
|
938
|
+
const skip = new Set(alreadyLoaded.map((m) => m.id));
|
|
939
|
+
const fresh = await myVectorDB.searchBatch(searches, { topK: 3 });
|
|
940
|
+
return fresh.filter((m) => !skip.has(m.id));
|
|
941
|
+
};
|
|
942
|
+
|
|
943
|
+
const myAgent = agent({
|
|
944
|
+
// ...
|
|
945
|
+
onMemoriesSearch,
|
|
946
|
+
});
|
|
947
|
+
```
|
|
948
|
+
|
|
949
|
+
### Actor usage (distiller or executor code)
|
|
950
|
+
|
|
951
|
+
```javascript
|
|
952
|
+
// Turn 1: kick off a batched lookup. Pass all queries in one call —
|
|
953
|
+
// don't loop or use Promise.all (the runtime rejects that as a policy
|
|
954
|
+
// violation; your callback should fan out internally).
|
|
955
|
+
await recall(['user preferences', 'project constraints']);
|
|
956
|
+
|
|
957
|
+
// Turn 2+: matched entries are now visible on `inputs.memories`.
|
|
958
|
+
const prefs = inputs.memories.find(m => m.id === 'user-prefs-v2');
|
|
959
|
+
```
|
|
960
|
+
|
|
961
|
+
### Behaviour
|
|
962
|
+
|
|
963
|
+
- `recall()` invokes `onMemoriesSearch` with `(searches, alreadyLoaded)` and returns `void`. `alreadyLoaded` is the current `inputs.memories` snapshot — filter your store results against it to skip duplicates. Results land on `inputs.memories` for subsequent turns.
|
|
964
|
+
- Entries are **deduped by `id`** (last-write-wins) and **sorted by `id`** for prefix-cache stability.
|
|
965
|
+
- Memories loaded by the distiller **thread automatically to the executor** — no second `recall()` needed for those entries.
|
|
966
|
+
- `recall()` may be called multiple times per turn; results accumulate. The merge dedupes against existing entries, so re-running the same search is cheap.
|
|
967
|
+
- **Lifetime is one `.forward()` call.** `inputs.memories` resets between calls. To carry memories across calls, persist them in your store and recall them again on the next call.
|
|
968
|
+
|
|
969
|
+
### Child agents
|
|
970
|
+
|
|
971
|
+
Child agents do **not** inherit `onMemoriesSearch` automatically. If a recursive `llmQuery` advanced child or a registered child agent should also have `recall()`, set `onMemoriesSearch` on that agent's options explicitly.
|
|
972
|
+
|
|
973
|
+
### Carrying memories across `.forward()` calls
|
|
974
|
+
|
|
975
|
+
`inputs.memories` resets between runs. To preserve continuity across calls, observe loads with `onUsedMemories` and replay them on the next call's first `recall()` (or via your store):
|
|
976
|
+
|
|
977
|
+
```typescript
|
|
978
|
+
const carried = new Map<string, string>();
|
|
979
|
+
|
|
980
|
+
const myAgent = agent({
|
|
981
|
+
// ...
|
|
982
|
+
onMemoriesSearch: async (searches) => {
|
|
983
|
+
const fresh = await myVectorDB.searchBatch(searches, { topK: 3 });
|
|
984
|
+
// Re-surface anything that landed on prior runs so the actor sees it
|
|
985
|
+
// alongside fresh matches.
|
|
986
|
+
const carriedAsResults = [...carried.entries()].map(([id, content]) => ({
|
|
987
|
+
id,
|
|
988
|
+
content,
|
|
989
|
+
}));
|
|
990
|
+
return [...carriedAsResults, ...fresh];
|
|
991
|
+
},
|
|
992
|
+
onUsedMemories: (results) => {
|
|
993
|
+
for (const r of results) carried.set(r.id, r.content);
|
|
994
|
+
},
|
|
995
|
+
});
|
|
996
|
+
```
|
|
997
|
+
|
|
998
|
+
## Skills Search
|
|
999
|
+
|
|
1000
|
+
Use `onSkillsSearch` when the agent needs to load skill guides — usage instructions, runbooks, domain conventions — into the executor's system prompt on demand. The actor decides which skills to fetch and when, so you don't pre-render every skill into every prompt.
|
|
1001
|
+
|
|
1002
|
+
When `onSkillsSearch` is set, the executor stage gains:
|
|
1003
|
+
|
|
1004
|
+
1. A "Loaded Skills" section in the system prompt that renders matched skill bodies (sorted by `name`).
|
|
1005
|
+
2. A `consult(searches: string[]): void` global the actor `await`s to load more skills. Loaded entries appear in the next turn's prompt — `consult()` itself returns nothing.
|
|
1006
|
+
|
|
1007
|
+
The distiller and responder do not see skills. Only the executor.
|
|
1008
|
+
|
|
1009
|
+
### Enabling
|
|
1010
|
+
|
|
1011
|
+
```typescript
|
|
1012
|
+
import { agent } from '@ax-llm/ax';
|
|
1013
|
+
import type { AxAgentSkillsSearchFn } from '@ax-llm/ax';
|
|
1014
|
+
|
|
1015
|
+
// Each result must be { name: string; content: string }
|
|
1016
|
+
const onSkillsSearch: AxAgentSkillsSearchFn = async (searches) => {
|
|
1017
|
+
return mySkillStore.searchBatch(searches, { topK: 2 });
|
|
1018
|
+
};
|
|
1019
|
+
|
|
1020
|
+
const myAgent = agent({
|
|
1021
|
+
// ...
|
|
1022
|
+
onSkillsSearch,
|
|
1023
|
+
});
|
|
1024
|
+
```
|
|
1025
|
+
|
|
1026
|
+
### Actor usage (executor code only)
|
|
1027
|
+
|
|
1028
|
+
```javascript
|
|
1029
|
+
// Pass all queries in one call — don't loop or use Promise.all (the
|
|
1030
|
+
// runtime rejects that as a policy violation; your callback should
|
|
1031
|
+
// fan out internally).
|
|
1032
|
+
await consult(['release-checklist', 'incident-response']);
|
|
1033
|
+
|
|
1034
|
+
// Next turn: the loaded skill bodies render under the "Loaded Skills"
|
|
1035
|
+
// system-prompt section, ready to apply directly.
|
|
1036
|
+
```
|
|
1037
|
+
|
|
1038
|
+
### Behaviour
|
|
1039
|
+
|
|
1040
|
+
- `consult()` invokes `onSkillsSearch` with the raw search strings and returns `void`. Matched skills land under "Loaded Skills" for the next turn.
|
|
1041
|
+
- Entries are deduped by `name` (last-write-wins) and sorted by `name` for prefix-cache stability.
|
|
1042
|
+
- **Skills persist on the agent's `currentSkillsPromptState` across `.forward()` calls** (unlike memories). Use `agent.getState()` / `setState(...)` to serialize/restore.
|
|
1043
|
+
- `consult()` may be called multiple times; results accumulate.
|
|
1044
|
+
- Child agents do **not** inherit `onSkillsSearch` — wire it explicitly per agent.
|
|
1045
|
+
|
|
1046
|
+
### Preloading Skills (`skills` option)
|
|
1047
|
+
|
|
1048
|
+
If the caller already knows which skills are relevant, pass them up-front instead of round-tripping through `consult()`:
|
|
1049
|
+
|
|
1050
|
+
- **Init-time** — `skills` on `AxAgentOptions` (constructor) seeds the executor's prompt at agent creation. They survive `setState(...)` resets, so they're always present from turn 1.
|
|
1051
|
+
- **Forward-time** — `skills` on the `forward(ai, values, { skills })` options merge in at the start of that call (executor stage only — distiller and responder ignore it).
|
|
1052
|
+
|
|
1053
|
+
Both accept the same shape `onSkillsSearch` returns: `readonly AxAgentSkillResult[]` (`{ name, content }[]`). Forward overrides init by `name` (same `Map.set` semantics as runtime-loaded skills). `onUsedSkills` is **not** fired for preset skills — that callback is for runtime `consult(...)` analytics.
|
|
1054
|
+
|
|
1055
|
+
```ts
|
|
1056
|
+
const agent = new AxAgent(
|
|
1057
|
+
{ signature: '...', agentIdentity: { name: 'release-bot', namespace: 'utils' } },
|
|
1058
|
+
{ skills: [{ name: 'release-checklist', content: '...' }] }
|
|
1059
|
+
);
|
|
1060
|
+
|
|
1061
|
+
await agent.forward(ai, values, {
|
|
1062
|
+
// overrides any same-named init skill, layers on top of runtime consult() loads
|
|
1063
|
+
skills: [{ name: 'incident-response', content: '...' }],
|
|
1064
|
+
});
|
|
1065
|
+
```
|
|
1066
|
+
|
|
1067
|
+
You can use `skills` without setting `onSkillsSearch` at all — handy for static guides where the actor never needs to fetch more.
|
|
1068
|
+
|
|
874
1069
|
## Option Layout
|
|
875
1070
|
|
|
876
1071
|
Use these top-level controls consistently:
|
|
877
1072
|
|
|
878
1073
|
- `mode`: controls whether `llmQuery(...)` stays simple or delegates to recursive child agents in advanced mode
|
|
879
1074
|
- `recursionOptions.maxDepth`: limits recursive `llmQuery(...)` depth
|
|
880
|
-
- `maxSubAgentCalls`: shared delegated-call budget across the whole run, including recursive children
|
|
1075
|
+
- `maxSubAgentCalls`: shared delegated-call budget across the whole run, including recursive children (default: 100)
|
|
881
1076
|
- `maxRuntimeChars`: runtime/output truncation ceiling for console logs, tool results, and interpreter output replay. The actual limit is computed dynamically each turn based on remaining context budget (see **Dynamic Output Truncation** below)
|
|
882
1077
|
- `summarizerOptions`: default model/options for the internal checkpoint summarizer
|
|
883
|
-
- `
|
|
884
|
-
- `
|
|
885
|
-
- `
|
|
886
|
-
- `
|
|
1078
|
+
- `contextOptions`: distiller-stage forward options (description, model, maxTurns, etc.). One of three peer stage-config bags.
|
|
1079
|
+
- `executorOptions`: executor-stage forward options such as `description`, `model`, `modelConfig`, `thinkingTokenBudget`, and `showThoughts`
|
|
1080
|
+
- `executorModelPolicy`: executor-only model override rules based on consecutive error turns or discovery fetches from listed namespaces
|
|
1081
|
+
- `responderOptions`: responder-stage forward options
|
|
1082
|
+
- `agentStatusCallback`: real-time progress updates from actor via `reportSuccess(message)` and `reportFailure(message)`
|
|
1083
|
+
- `onFunctionCall`: observe every runtime function call (`{ name, qualifiedName, args, kind: 'internal' | 'external' }`)
|
|
887
1084
|
- `judgeOptions`: built-in judge options for `agent.optimize(...)`; for tuning workflows use the `ax-agent-optimize` skill
|
|
888
1085
|
- `bubbleErrors`: error classes that propagate out of function handlers, actor code, and llmQuery sub-agents directly to `.forward()` instead of being caught and returned as `[ERROR]` strings
|
|
889
1086
|
|
|
@@ -906,11 +1103,15 @@ const researchAgent = agent('query:string -> answer:string', {
|
|
|
906
1103
|
preset: 'checkpointed',
|
|
907
1104
|
budget: 'balanced',
|
|
908
1105
|
},
|
|
909
|
-
|
|
1106
|
+
contextOptions: {
|
|
1107
|
+
model: 'gpt-5.4-mini',
|
|
1108
|
+
maxTurns: 3,
|
|
1109
|
+
},
|
|
1110
|
+
executorOptions: {
|
|
910
1111
|
description: 'Use tools first and keep JS steps small.',
|
|
911
1112
|
model: 'gpt-5.4-mini',
|
|
912
1113
|
},
|
|
913
|
-
|
|
1114
|
+
executorModelPolicy: [
|
|
914
1115
|
{
|
|
915
1116
|
model: 'gpt-5.4',
|
|
916
1117
|
aboveErrorTurns: 2,
|
|
@@ -928,10 +1129,10 @@ Semantics:
|
|
|
928
1129
|
- `mode` stays top-level; there is no `recursionOptions.mode`.
|
|
929
1130
|
- `maxRuntimeChars` sets the truncation ceiling and is separate from `contextPolicy.budget`. The effective limit per turn is computed dynamically (see below).
|
|
930
1131
|
- `summarizerOptions` tunes only the internal checkpoint summarizer. It does not change actor or responder model selection.
|
|
931
|
-
- The current merged actor model stays the default base model. `
|
|
932
|
-
- `
|
|
933
|
-
- Recursive child agents can inherit `
|
|
934
|
-
- `
|
|
1132
|
+
- The current merged actor model stays the default base model. `executorModelPolicy` only overrides it when a rule matches.
|
|
1133
|
+
- `executorModelPolicy` only switches the actor model. It does not change `responderOptions.model`.
|
|
1134
|
+
- Recursive child agents can inherit `executorModelPolicy`; use a child override only when that child needs different routing behavior.
|
|
1135
|
+
- `executorModelPolicy` entries are ordered from weaker to stronger. If multiple rules match, the last matching entry wins.
|
|
935
1136
|
- If one entry also defines `namespaces`, any successful `discoverFunctions(...)` fetch from one of those namespaces marks the rule as matched starting on the next actor turn.
|
|
936
1137
|
|
|
937
1138
|
When choosing these options for a user:
|
|
@@ -939,8 +1140,8 @@ When choosing these options for a user:
|
|
|
939
1140
|
- Do not add `mode: 'advanced'` just because recursion exists as a feature. Add it only when delegated children need their own tool/discovery/runtime loop.
|
|
940
1141
|
- Do not add `recursionOptions` at all if the user does not need recursive delegation.
|
|
941
1142
|
- Do not add `judgeOptions` in normal agent examples; reserve that for optimize/eval workflows.
|
|
942
|
-
- Keep `
|
|
943
|
-
- Use `
|
|
1143
|
+
- Keep `executorOptions` focused on actor-only forward concerns such as `description`, `model`, `modelConfig`, `thinkingTokenBudget`, and `showThoughts`.
|
|
1144
|
+
- Use `executorModelPolicy` when the actor is the bottleneck and you want the responder to stay fixed.
|
|
944
1145
|
|
|
945
1146
|
## Dynamic Output Truncation
|
|
946
1147
|
|
|
@@ -959,15 +1160,17 @@ This means the actor sees structurally informative output even when the char bud
|
|
|
959
1160
|
|
|
960
1161
|
Users do not need to configure this behavior — it is automatic. `maxRuntimeChars` sets the upper bound; the dynamic system only ever reduces, never exceeds it.
|
|
961
1162
|
|
|
962
|
-
##
|
|
1163
|
+
## Stage Prompt Controls
|
|
963
1164
|
|
|
964
|
-
|
|
1165
|
+
The pipeline has three peer stage-config bags: `contextOptions` (distiller), `executorOptions` (executor), `responderOptions` (responder). Each accepts the same shape: `description`, `model`, `modelConfig`, `excludeFields`, plus other forward options.
|
|
965
1166
|
|
|
966
1167
|
Key fields:
|
|
967
1168
|
|
|
968
|
-
- `
|
|
969
|
-
- `
|
|
970
|
-
- `
|
|
1169
|
+
- `contextOptions.description`: append extra distiller-specific instructions; useful for telling the distiller about domain conventions for narrowing context.
|
|
1170
|
+
- `executorOptions.description`: append extra executor-specific instructions; the typical place for tool-use guidance.
|
|
1171
|
+
- `responderOptions.description`: append extra responder-specific instructions; useful for output-formatting rules.
|
|
1172
|
+
- `contextOptions.model` / `executorOptions.model` / `responderOptions.model`: split model choice across the three stages.
|
|
1173
|
+
- `executorModelPolicy`: auto-switch only the executor when the run is on a consecutive error streak or discovery fetches land in specific namespaces.
|
|
971
1174
|
|
|
972
1175
|
Good split-model pattern:
|
|
973
1176
|
|
|
@@ -976,7 +1179,7 @@ const researchAgent = agent('query:string -> answer:string', {
|
|
|
976
1179
|
contextFields: ['query'],
|
|
977
1180
|
runtime,
|
|
978
1181
|
contextPolicy: { preset: 'checkpointed', budget: 'balanced' },
|
|
979
|
-
|
|
1182
|
+
executorOptions: {
|
|
980
1183
|
model: 'gpt-5.4',
|
|
981
1184
|
},
|
|
982
1185
|
responderOptions: {
|
|
@@ -990,8 +1193,8 @@ Model guidance:
|
|
|
990
1193
|
- Put the stronger model on the actor when the task depends on multi-turn exploration, discovery, runtime state reuse, or compressed replay.
|
|
991
1194
|
- Put the stronger model on the responder only when the hard part is final synthesis/formatting rather than exploration.
|
|
992
1195
|
- For cost-sensitive setups, a common pattern is stronger actor + cheaper responder, not the other way around.
|
|
993
|
-
- Prefer `
|
|
994
|
-
- Pair `contextPolicy: { preset: 'checkpointed', budget: 'balanced' }` with `
|
|
1196
|
+
- Prefer `executorModelPolicy` over globally upgrading the whole agent when the actor only needs help after context grows or the run starts thrashing.
|
|
1197
|
+
- Pair `contextPolicy: { preset: 'checkpointed', budget: 'balanced' }` with `executorModelPolicy` when you want full replay first and actor-only upgrades triggered by errors or discovered tool domains.
|
|
995
1198
|
|
|
996
1199
|
Invalid pattern:
|
|
997
1200
|
|
|
@@ -1004,9 +1207,9 @@ await final("Summarize severity findings", { snippets });
|
|
|
1004
1207
|
|
|
1005
1208
|
Reason: this mixes observation and follow-up work in one turn.
|
|
1006
1209
|
|
|
1007
|
-
##
|
|
1210
|
+
## Threading Parent Fields Into Child Agents
|
|
1008
1211
|
|
|
1009
|
-
If a child agent requires a parent field such as `audience`,
|
|
1212
|
+
If a child agent requires a parent field such as `audience`, declare it on the child's signature and pass it explicitly when calling the child from the actor:
|
|
1010
1213
|
|
|
1011
1214
|
```typescript
|
|
1012
1215
|
const writingCoach = agent(
|
|
@@ -1015,6 +1218,7 @@ const writingCoach = agent(
|
|
|
1015
1218
|
agentIdentity: {
|
|
1016
1219
|
name: 'Writing Coach',
|
|
1017
1220
|
description: 'Polishes summaries for a target audience',
|
|
1221
|
+
namespace: 'team',
|
|
1018
1222
|
},
|
|
1019
1223
|
contextFields: [],
|
|
1020
1224
|
}
|
|
@@ -1023,50 +1227,55 @@ const writingCoach = agent(
|
|
|
1023
1227
|
const analyst = agent(
|
|
1024
1228
|
'context:string, audience:string, query:string -> answer:string',
|
|
1025
1229
|
{
|
|
1026
|
-
|
|
1027
|
-
fields: { shared: ['audience'] },
|
|
1230
|
+
functions: [writingCoach],
|
|
1028
1231
|
contextFields: ['context'],
|
|
1029
1232
|
}
|
|
1030
1233
|
);
|
|
1031
1234
|
```
|
|
1032
1235
|
|
|
1033
|
-
Generated runtime call:
|
|
1236
|
+
Generated runtime call (note: namespace comes from the child's `agentIdentity.namespace`; falls back to `utils` if unset):
|
|
1034
1237
|
|
|
1035
1238
|
```javascript
|
|
1036
|
-
const polished = await
|
|
1239
|
+
const polished = await team.writingCoach({
|
|
1240
|
+
draft: summary,
|
|
1241
|
+
audience: inputs.audience,
|
|
1242
|
+
});
|
|
1037
1243
|
```
|
|
1038
1244
|
|
|
1039
1245
|
Rules:
|
|
1040
1246
|
|
|
1041
|
-
-
|
|
1042
|
-
-
|
|
1043
|
-
- Do not
|
|
1247
|
+
- Pass parent fields explicitly via the call site — `inputs.<field>`.
|
|
1248
|
+
- If many children need the same field on every call, use `inputUpdateCallback` to inject the value before each executor turn.
|
|
1249
|
+
- Do not assume any auto-propagation: child agents receive only the args the actor passes.
|
|
1044
1250
|
|
|
1045
|
-
##
|
|
1251
|
+
## Grouped Function Modules
|
|
1046
1252
|
|
|
1047
|
-
|
|
1253
|
+
For discovery mode, you can group functions into modules using the `AxAgentFunctionGroup` shape — handy when you want a clean `kb.find(...)`, `metrics.score(...)` namespace tree without setting `namespace` on every individual `fn(...)`:
|
|
1048
1254
|
|
|
1049
1255
|
```typescript
|
|
1050
1256
|
const parent = agent('query:string -> answer:string', {
|
|
1051
|
-
|
|
1052
|
-
|
|
1053
|
-
|
|
1054
|
-
|
|
1055
|
-
|
|
1056
|
-
|
|
1057
|
-
|
|
1058
|
-
|
|
1059
|
-
|
|
1060
|
-
|
|
1257
|
+
functions: [
|
|
1258
|
+
{
|
|
1259
|
+
namespace: 'kb',
|
|
1260
|
+
description: 'Knowledge base lookups',
|
|
1261
|
+
functions: [findSnippetsFn, searchPagesFn],
|
|
1262
|
+
},
|
|
1263
|
+
{
|
|
1264
|
+
namespace: 'metrics',
|
|
1265
|
+
description: 'Scoring and coverage helpers',
|
|
1266
|
+
functions: [scoreCoverageFn],
|
|
1267
|
+
},
|
|
1268
|
+
],
|
|
1269
|
+
functionDiscovery: true,
|
|
1061
1270
|
contextFields: [],
|
|
1062
1271
|
});
|
|
1063
1272
|
```
|
|
1064
1273
|
|
|
1065
1274
|
Rules:
|
|
1066
1275
|
|
|
1067
|
-
- `
|
|
1068
|
-
- `
|
|
1069
|
-
-
|
|
1276
|
+
- A group is `{ namespace, description, functions: [...] }`. The group's `namespace` and `description` show up in `discoverModules(...)` markdown.
|
|
1277
|
+
- Mix groups and ungrouped entries freely in `functions: [...]` — child agents and `fn(...)` tools sit alongside group entries.
|
|
1278
|
+
- There is no `local` / `shared` / `globallyShared` propagation. Each agent owns its own `functions: [...]`; pass shared tools to children explicitly.
|
|
1070
1279
|
|
|
1071
1280
|
## Tuning Hand-off
|
|
1072
1281
|
|
|
@@ -1197,13 +1406,25 @@ agentIdentity?: {
|
|
|
1197
1406
|
```
|
|
1198
1407
|
|
|
1199
1408
|
- `name` is normalized to camelCase for child-agent function names.
|
|
1409
|
+
- `name` and `description` are included in the Actor and Responder prompts as the user-facing agent identity.
|
|
1200
1410
|
- `namespace` changes the child-agent module from default `agents` to a custom module such as `team`.
|
|
1201
1411
|
|
|
1202
1412
|
### `AxAgentOptions`
|
|
1203
1413
|
|
|
1414
|
+
Each `contextFields` entry is either a plain field name string or an object controlling how much of the value is inlined into the distiller prompt:
|
|
1415
|
+
|
|
1416
|
+
- `{ field, promptMaxChars: N }` — **threshold inline**: inlined only when the value's serialized size ≤ N chars; omitted entirely (runtime-only) when larger. Works with any value type.
|
|
1417
|
+
- `{ field, keepInPromptChars: N, reverseTruncate?: boolean }` — **guaranteed excerpt**: always inlined, truncated to N chars with a `...[truncated M chars]` marker. `reverseTruncate: true` keeps the *last* N chars instead of the first. Requires a string value.
|
|
1418
|
+
|
|
1419
|
+
Use `promptMaxChars` when partial data is worse than no data (e.g. JSON objects). Use `keepInPromptChars` when a prefix or suffix alone is useful (e.g. a document header, or a log tail with `reverseTruncate: true`). The two options are mutually exclusive on a single field.
|
|
1420
|
+
|
|
1204
1421
|
```typescript
|
|
1205
1422
|
{
|
|
1206
|
-
contextFields: readonly (
|
|
1423
|
+
contextFields: readonly (
|
|
1424
|
+
| string
|
|
1425
|
+
| { field: string; promptMaxChars?: number }
|
|
1426
|
+
| { field: string; keepInPromptChars: number; reverseTruncate?: boolean }
|
|
1427
|
+
)[];
|
|
1207
1428
|
|
|
1208
1429
|
agents?: {
|
|
1209
1430
|
local?: AxAnyAgentic[];
|
|
@@ -1229,18 +1450,17 @@ agentIdentity?: {
|
|
|
1229
1450
|
|
|
1230
1451
|
runtime?: AxCodeRuntime;
|
|
1231
1452
|
promptLevel?: 'default' | 'detailed';
|
|
1232
|
-
maxSubAgentCalls?: number;
|
|
1453
|
+
maxSubAgentCalls?: number; // global cap (default: 100)
|
|
1233
1454
|
maxBatchedLlmQueryConcurrency?: number;
|
|
1234
1455
|
maxTurns?: number;
|
|
1235
1456
|
maxRuntimeChars?: number;
|
|
1236
1457
|
contextPolicy?: AxContextPolicyConfig;
|
|
1237
1458
|
summarizerOptions?: Omit<AxProgramForwardOptions<string>, 'functions'>;
|
|
1238
|
-
|
|
1239
|
-
actorTurnCallback?: (turn: {
|
|
1459
|
+
executorTurnCallback?: (turn: {
|
|
1240
1460
|
turn: number;
|
|
1241
1461
|
actionLogEntryCount: number;
|
|
1242
1462
|
guidanceLogEntryCount: number;
|
|
1243
|
-
|
|
1463
|
+
executorResult: Record<string, unknown>;
|
|
1244
1464
|
code: string;
|
|
1245
1465
|
result: unknown;
|
|
1246
1466
|
output: string;
|
|
@@ -1248,8 +1468,19 @@ agentIdentity?: {
|
|
|
1248
1468
|
thought?: string;
|
|
1249
1469
|
}) => void | Promise<void>;
|
|
1250
1470
|
inputUpdateCallback?: (currentInputs: Record<string, unknown>) => Promise<Record<string, unknown> | undefined> | Record<string, unknown> | undefined;
|
|
1471
|
+
onFunctionCall?: (call: {
|
|
1472
|
+
name: string;
|
|
1473
|
+
qualifiedName: string;
|
|
1474
|
+
args: Record<string, unknown>;
|
|
1475
|
+
kind: 'internal' | 'external';
|
|
1476
|
+
}) => void | Promise<void>;
|
|
1477
|
+
onMemoriesSearch?: AxAgentMemoriesSearchFn; // (searches: readonly string[]) => readonly AxAgentMemoryResult[] | Promise<...>
|
|
1478
|
+
onUsedMemories?: (results: readonly AxAgentMemoryResult[]) => void | Promise<void>;
|
|
1479
|
+
onSkillsSearch?: AxAgentSkillsSearchFn; // (searches: readonly string[]) => readonly AxAgentSkillResult[] | Promise<...>
|
|
1480
|
+
onUsedSkills?: (results: readonly AxAgentSkillResult[]) => void | Promise<void>;
|
|
1481
|
+
skills?: readonly AxAgentSkillResult[]; // preload skills at construction; also accepted at forward()-time (executor stage only)
|
|
1251
1482
|
mode?: 'simple' | 'advanced';
|
|
1252
|
-
|
|
1483
|
+
executorModelPolicy?: readonly [
|
|
1253
1484
|
| {
|
|
1254
1485
|
model: string;
|
|
1255
1486
|
aboveErrorTurns: number;
|
|
@@ -1276,15 +1507,16 @@ agentIdentity?: {
|
|
|
1276
1507
|
recursionOptions?: Partial<Omit<AxProgramForwardOptions, 'functions'>> & {
|
|
1277
1508
|
maxDepth?: number;
|
|
1278
1509
|
};
|
|
1279
|
-
|
|
1280
|
-
|
|
1510
|
+
contextOptions?: AxStageOptions;
|
|
1511
|
+
executorOptions?: AxStageOptions;
|
|
1512
|
+
responderOptions?: AxStageOptions;
|
|
1281
1513
|
judgeOptions?: Partial<AxJudgeOptions>;
|
|
1282
1514
|
bubbleErrors?: ReadonlyArray<new (...args: any[]) => Error>;
|
|
1283
1515
|
}
|
|
1284
1516
|
```
|
|
1285
1517
|
|
|
1286
|
-
- `
|
|
1287
|
-
- `
|
|
1518
|
+
- `executorTurnCallback` fires for the root agent and for recursive child agents that run actor turns.
|
|
1519
|
+
- `executorModelPolicy` applies to the actor loop and can be inherited by recursive child agents unless you override it there.
|
|
1288
1520
|
- `namespaces` matches exact discovery namespaces from successful `discoverFunctions(...)` lookups and starts affecting model choice on the next actor turn.
|
|
1289
1521
|
- Consecutive error turns reset after a successful non-error turn and when checkpoint summarization refreshes to a new fingerprint.
|
|
1290
1522
|
- `maxSubAgentCalls` is a shared delegated-call budget across the entire run.
|
|
@@ -1308,25 +1540,18 @@ Constructor options for `new AxJSRuntime(opts)`. All defaults are secure — see
|
|
|
1308
1540
|
|
|
1309
1541
|
## Observability: getChatLog() and getUsage()
|
|
1310
1542
|
|
|
1311
|
-
`AxAgent` exposes
|
|
1543
|
+
`AxAgent` exposes actor and responder sub-programs. `getChatLog()` returns the same flat `AxChatLogEntry[]` shape as `AxGen` and `AxFlow`; use each entry's optional `name` field to distinguish `distiller`, `executor`, and `responder`. `getUsage()` still returns token usage split by actor/responder.
|
|
1312
1544
|
|
|
1313
1545
|
### getChatLog()
|
|
1314
1546
|
|
|
1315
|
-
Returns the full normalized chat history
|
|
1547
|
+
Returns the full normalized chat history after any `.forward()` call. Each entry is one `ai.chat()` round-trip. Actor stages accumulate one entry per turn; the responder typically has one entry.
|
|
1316
1548
|
|
|
1317
1549
|
```typescript
|
|
1318
1550
|
const log = myAgent.getChatLog();
|
|
1319
|
-
//
|
|
1320
|
-
|
|
1321
|
-
for (const entry of log.actor) {
|
|
1322
|
-
console.log('Actor model:', entry.model);
|
|
1323
|
-
for (const msg of entry.messages) {
|
|
1324
|
-
console.log(`[${msg.role}]`, msg.content);
|
|
1325
|
-
}
|
|
1326
|
-
}
|
|
1551
|
+
// readonly AxChatLogEntry[]
|
|
1327
1552
|
|
|
1328
|
-
for (const entry of log
|
|
1329
|
-
console.log(
|
|
1553
|
+
for (const entry of log) {
|
|
1554
|
+
console.log(entry.name, entry.model);
|
|
1330
1555
|
for (const msg of entry.messages) {
|
|
1331
1556
|
console.log(`[${msg.role}]`, msg.content);
|
|
1332
1557
|
}
|
|
@@ -1343,9 +1568,11 @@ type AxChatLogMessage =
|
|
|
1343
1568
|
| { role: 'tool'; name: string; content: string };
|
|
1344
1569
|
|
|
1345
1570
|
type AxChatLogEntry = {
|
|
1571
|
+
name?: string; // e.g. "distiller", "executor", "responder"
|
|
1346
1572
|
model: string;
|
|
1347
1573
|
messages: AxChatLogMessage[];
|
|
1348
1574
|
modelUsage?: AxProgramUsage;
|
|
1575
|
+
stage?: 'ctx' | 'task';
|
|
1349
1576
|
};
|
|
1350
1577
|
```
|
|
1351
1578
|
|
|
@@ -1373,11 +1600,11 @@ myAgent.resetUsage();
|
|
|
1373
1600
|
|
|
1374
1601
|
```typescript
|
|
1375
1602
|
// AxAgent
|
|
1376
|
-
agent.getChatLog():
|
|
1603
|
+
agent.getChatLog(): readonly AxChatLogEntry[]
|
|
1377
1604
|
agent.getUsage(): { actor: AxProgramUsage[]; responder: AxProgramUsage[] }
|
|
1378
1605
|
agent.resetUsage(): void
|
|
1379
1606
|
|
|
1380
|
-
// AxGen
|
|
1607
|
+
// AxGen / AxFlow
|
|
1381
1608
|
gen.getChatLog(): readonly AxChatLogEntry[]
|
|
1382
1609
|
gen.getUsage(): AxProgramUsage[]
|
|
1383
1610
|
```
|
|
@@ -1397,6 +1624,7 @@ Fetch these for full working code:
|
|
|
1397
1624
|
- [RLM Adaptive Replay](https://raw.githubusercontent.com/ax-llm/ax/refs/heads/main/src/examples/rlm-adaptive-replay.ts) — adaptive replay
|
|
1398
1625
|
- [RLM Live Runtime State](https://raw.githubusercontent.com/ax-llm/ax/refs/heads/main/src/examples/rlm-live-runtime-state.ts) — structured runtime-state rendering
|
|
1399
1626
|
- [RLM Clarification Resume](https://raw.githubusercontent.com/ax-llm/ax/refs/heads/main/src/examples/rlm-clarification-resume.ts) — clarification exception plus `getState()` / `setState(...)`
|
|
1627
|
+
- [RLM Memories and Skills](https://raw.githubusercontent.com/ax-llm/ax/refs/heads/main/src/examples/rlm-memories-and-skills.ts) — `onMemoriesSearch` + `recall()` and `onSkillsSearch` + `consult()` with observability via `onUsedMemories` / `onUsedSkills`
|
|
1400
1628
|
- [Customer Support](https://raw.githubusercontent.com/ax-llm/ax/refs/heads/main/src/examples/customer-support.ts) — classification agent
|
|
1401
1629
|
- [Abort Patterns](https://raw.githubusercontent.com/ax-llm/ax/refs/heads/main/src/examples/abort-patterns.ts) — abort handling
|
|
1402
1630
|
|
|
@@ -1409,3 +1637,11 @@ Fetch these for full working code:
|
|
|
1409
1637
|
- Do not combine `console.log(...)` with `final(...)`.
|
|
1410
1638
|
- Do not forget `fields.shared` when child agents depend on parent inputs.
|
|
1411
1639
|
- Do not add `bubbleErrors` for ordinary recoverable tool errors; those should stay as `[ERROR]` strings so the actor can handle them.
|
|
1640
|
+
- Do not call `recall()` from the responder stage — it is only available in distiller and executor.
|
|
1641
|
+
- Do not assign the result of `await recall(...)` or `await consult(...)` — both return `void`. Read `inputs.memories` next turn (or the **Loaded Skills** section for `consult`) to see what landed.
|
|
1642
|
+
- Do not loop `recall()` calls or wrap them in `Promise.all` — the runtime rejects that as a policy violation. Pass all queries in one array to a single `await recall([...])`.
|
|
1643
|
+
- Do not assume child agents inherit `onMemoriesSearch` or `onSkillsSearch` — set each one explicitly on each agent that needs `recall()` / `consult()`.
|
|
1644
|
+
- Do not call `consult()` from the distiller or responder stages — it is only available in the executor.
|
|
1645
|
+
- Do not loop `consult()` calls or wrap them in `Promise.all` — same policy as `recall()`. Pass all queries in one array.
|
|
1646
|
+
- Do not pass `onMemoriesSearch` results via `fields.shared` as a workaround — use the built-in `recall()` primitive instead.
|
|
1647
|
+
- Do not assume `inputs.memories` persists across `.forward()` calls — its lifetime is one run. Persist memories in your store and recall them again on subsequent calls.
|