@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.
@@ -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: "20.0.2"
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
- - Assume the child-agent module is `agents` unless `agentIdentity.namespace` is set.
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 `actorModelPolicy` when the actor may need to upgrade after repeated error turns or discovery in specific namespaces without also upgrading the responder.
31
- - Use `actorTurnCallback` 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 success(message)` and `await failed(message)` calls.
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" -> use `agents.local`, and add `fields.shared` only when parent inputs truly need to flow into children.
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 `actorModelPolicy` and keep the responder model separate.
44
- - "Need debugging or traceability" -> start with `debug: true` or `actorTurnCallback`; do not add both unless the user clearly wants both prompt/runtime visibility and structured telemetry.
45
- - "Need real-time progress updates" -> add `agentStatusCallback` so the actor can call `await success(message)` and `await failed(message)` to report sub-task progress.
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
- Treat `AxAgent` as a long-running JavaScript REPL that the actor steers over multiple turns, not as a fresh script generator on every turn.
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
- - `actorModelPolicy` decides when the actor switches to an override model without changing the responder.
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 `actorModelPolicy` so only the actor upgrades under pressure.
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
- - `actorModelPolicy` is usually better than globally upgrading the whole agent when the bottleneck is actor exploration rather than responder synthesis.
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
- - If `agentIdentity.namespace` is set, call child agents through that module, not `agents`.
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 And Module Namespace
174
+ ## Child Agents As Tools
160
175
 
161
- Default child-agent module:
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
- agents: { local: [writer] },
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 agents.writer({ draft: '...' });
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
- Generated runtime call:
200
+ Without `agentIdentity.namespace`, the child lands under `utils.<name>` like any other tool:
207
201
 
208
202
  ```javascript
209
- const result = await team.writer({ draft: '...' });
203
+ const result = await utils.writer({ draft: '...' });
210
204
  ```
211
205
 
212
206
  Rules:
213
207
 
214
- - Default child-agent module is `agents`.
215
- - If `agentIdentity.namespace` is set, that becomes the child-agent module.
216
- - Do not hardcode `agents.<name>(...)` when a custom namespace is configured.
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: { local: [dbTool] },
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
- agents: { local: [writer] },
525
- functions: {
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 runtimes. |
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: { local: tools },
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 `inspect_runtime()` from inside `test(...)` snippets yet; prefer checking runtime globals directly inside the snippet.
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 `inspect_runtime()` and can add an inspect hint automatically when the rendered actor prompt starts getting large relative to the selected budget.
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 `actorTurnCallback` when the caller needs structured telemetry for each actor turn.
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
- - `actorResult`: the full actor payload, including actor-owned output fields when `actorFields` are configured
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
- actorTurnCallback: ({
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
- actorOptions: {
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 success(message)` and `await failed(message)` in its JavaScript turns.
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 `success(message)` and `failed(message)` as available runtime functions.
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
- - `success` and `failed` are reserved runtime names when the callback is configured.
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
- - `actorOptions`: actor-only forward options such as `description`, `model`, `modelConfig`, `thinkingTokenBudget`, and `showThoughts`
884
- - `actorModelPolicy`: actor-only model override rules based on consecutive error turns or discovery fetches from listed namespaces
885
- - `responderOptions`: responder-only forward options
886
- - `agentStatusCallback`: real-time progress updates from actor via `success(message)` and `failed(message)`
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
- actorOptions: {
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
- actorModelPolicy: [
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. `actorModelPolicy` only overrides it when a rule matches.
932
- - `actorModelPolicy` only switches the actor model. It does not change `responderOptions.model`.
933
- - Recursive child agents can inherit `actorModelPolicy`; use a child override only when that child needs different routing behavior.
934
- - `actorModelPolicy` entries are ordered from weaker to stronger. If multiple rules match, the last matching entry wins.
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 `actorOptions` focused on actor-only forward concerns such as `description`, `model`, `modelConfig`, `thinkingTokenBudget`, and `showThoughts`.
943
- - Use `actorModelPolicy` when the actor is the bottleneck and you want the responder to stay fixed.
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
- ## Actor Prompt Controls
1163
+ ## Stage Prompt Controls
963
1164
 
964
- Use `actorOptions` for actor-only forward options and `responderOptions` for responder-only tuning.
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
- - `actorOptions.description`: append extra actor-specific instructions without changing the responder prompt
969
- - `actorOptions.model` / `responderOptions.model`: split model choice across actor and responder when needed
970
- - `actorModelPolicy`: auto-switch only the actor when the run is on a consecutive error streak or discovery fetches land in specific namespaces
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
- actorOptions: {
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 `actorModelPolicy` over globally upgrading the whole agent when the actor only needs help after context grows or the run starts thrashing.
994
- - Pair `contextPolicy: { preset: 'checkpointed', budget: 'balanced' }` with `actorModelPolicy` when you want full replay first and actor-only upgrades triggered by errors or discovered tool domains.
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
- ## Shared Fields
1210
+ ## Threading Parent Fields Into Child Agents
1008
1211
 
1009
- If a child agent requires a parent field such as `audience`, prefer shared fields:
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
- agents: { local: [writingCoach] },
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 agents.writingCoach({ draft: summary });
1239
+ const polished = await team.writingCoach({
1240
+ draft: summary,
1241
+ audience: inputs.audience,
1242
+ });
1037
1243
  ```
1038
1244
 
1039
1245
  Rules:
1040
1246
 
1041
- - Use `fields.shared` for direct children.
1042
- - Use `fields.globallyShared` for all descendants.
1043
- - Do not manually thread a parent field on every child call when shared fields fit the use case.
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
- ## Shared Agents And Shared Functions
1251
+ ## Grouped Function Modules
1046
1252
 
1047
- Use grouped config:
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
- agents: {
1052
- local: [worker],
1053
- shared: [logger],
1054
- globallyShared: [auditor],
1055
- },
1056
- functions: {
1057
- local: [searchTool],
1058
- shared: [scoreTool],
1059
- globallyShared: [traceTool],
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
- - `agents.shared` and `functions.shared` propagate one level down.
1068
- - `agents.globallyShared` and `functions.globallyShared` propagate to all descendants.
1069
- - Use `excluded` when a child should not receive a propagated field, agent, or function.
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 (string | { field: string; promptMaxChars?: number })[];
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
- actorFields?: string[];
1239
- actorTurnCallback?: (turn: {
1459
+ executorTurnCallback?: (turn: {
1240
1460
  turn: number;
1241
1461
  actionLogEntryCount: number;
1242
1462
  guidanceLogEntryCount: number;
1243
- actorResult: Record<string, unknown>;
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
- actorModelPolicy?: readonly [
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
- actorOptions?: Partial<AxProgramForwardOptions & { description?: string }>;
1280
- responderOptions?: Partial<AxProgramForwardOptions & { description?: string }>;
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
- - `actorTurnCallback` fires for the root agent and for recursive child agents that run actor turns.
1287
- - `actorModelPolicy` applies to the actor loop and can be inherited by recursive child agents unless you override it there.
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 two sub-programs (Actor and Responder). Both `getChatLog()` and `getUsage()` return an object split by role unlike `AxGen`, which returns a flat array.
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 split by actor/responder after any `.forward()` call. Each entry is one `ai.chat()` round-trip. The actor accumulates one entry per turn; the responder typically has one.
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
- // { actor: AxChatLogEntry[], responder: AxChatLogEntry[] }
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.responder) {
1329
- console.log('Responder model:', entry.model);
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(): { actor: readonly AxChatLogEntry[]; responder: readonly AxChatLogEntry[] }
1603
+ agent.getChatLog(): readonly AxChatLogEntry[]
1377
1604
  agent.getUsage(): { actor: AxProgramUsage[]; responder: AxProgramUsage[] }
1378
1605
  agent.resetUsage(): void
1379
1606
 
1380
- // AxGen (flat — no split)
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.