@genesislcap/ai-assistant 14.421.1 → 14.422.0

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.
Files changed (37) hide show
  1. package/dist/ai-assistant.api.json +191 -1
  2. package/dist/ai-assistant.d.ts +60 -0
  3. package/dist/dts/components/chat-driver/chat-driver.d.ts +33 -0
  4. package/dist/dts/components/chat-driver/chat-driver.d.ts.map +1 -1
  5. package/dist/dts/components/orchestrating-driver/orchestrating-driver.d.ts.map +1 -1
  6. package/dist/dts/config/config.d.ts +20 -0
  7. package/dist/dts/config/config.d.ts.map +1 -1
  8. package/dist/dts/main/main.d.ts +6 -0
  9. package/dist/dts/main/main.d.ts.map +1 -1
  10. package/dist/dts/main/main.styles.d.ts.map +1 -1
  11. package/dist/dts/main/main.template.d.ts +16 -0
  12. package/dist/dts/main/main.template.d.ts.map +1 -1
  13. package/dist/dts/state/ai-assistant-slice.d.ts +6 -0
  14. package/dist/dts/state/ai-assistant-slice.d.ts.map +1 -1
  15. package/dist/dts/state/session-store.d.ts +2 -0
  16. package/dist/dts/state/session-store.d.ts.map +1 -1
  17. package/dist/dts/utils/history-transform.d.ts +13 -0
  18. package/dist/dts/utils/history-transform.d.ts.map +1 -0
  19. package/dist/esm/components/chat-driver/chat-driver.js +127 -16
  20. package/dist/esm/components/orchestrating-driver/orchestrating-driver.js +8 -20
  21. package/dist/esm/config/config.js +18 -1
  22. package/dist/esm/main/main.js +43 -11
  23. package/dist/esm/main/main.styles.js +62 -0
  24. package/dist/esm/main/main.template.js +122 -71
  25. package/dist/esm/state/ai-assistant-slice.js +8 -0
  26. package/dist/esm/utils/history-transform.js +35 -0
  27. package/dist/tsconfig.tsbuildinfo +1 -1
  28. package/docs/sub_agent.md +149 -211
  29. package/package.json +16 -16
  30. package/src/components/chat-driver/chat-driver.ts +169 -15
  31. package/src/components/orchestrating-driver/orchestrating-driver.ts +10 -22
  32. package/src/config/config.ts +24 -0
  33. package/src/main/main.styles.ts +62 -0
  34. package/src/main/main.template.ts +189 -117
  35. package/src/main/main.ts +43 -9
  36. package/src/state/ai-assistant-slice.ts +12 -0
  37. package/src/utils/history-transform.ts +40 -0
package/docs/sub_agent.md CHANGED
@@ -1,310 +1,248 @@
1
- # Sub-Agent Architecture
1
+ # Sub-Agents
2
2
 
3
3
  ## Overview
4
4
 
5
- Sub-agents are specialist agents embedded inside a parent agent. Tool handlers on the parent invoke them explicitly via `requestSubAgent` — a sibling to the existing `requestInteraction` API. Each sub-agent runs its own full tool loop to completion and returns its final response to the calling handler as a resolved promise. From the LLM's perspective, it called a normal tool and got a result back — it has no visibility into the sub-agent that produced it.
5
+ Sub-agents are specialist agents embedded inside a parent agent. Tool handlers on the parent invoke them explicitly via `requestSubAgent` — a sibling to the existing `requestInteraction` API. Each sub-agent runs its own full tool loop to completion and returns its result to the calling handler as a resolved promise.
6
6
 
7
- The goal is to improve quality and reduce hallucinations by routing bounded, well-defined tasks (data extraction, validation, enrichment, judge passes) to purpose-built agents with focused prompts and tight tool sets, without burdening the parent's context window.
7
+ From the LLM's perspective, it called a normal tool and received a result it has no visibility into the sub-agent that produced it. Sub-agents are an implementation detail of a tool call, not a visible actor in the conversation.
8
8
 
9
- ---
10
-
11
- ## Motivating Example
12
-
13
- **Trade File Extraction sub-agent**
14
-
15
- A user attaches a CSV of trades they want booked. The parent (Trades Agent) is good at booking and querying trades but is not the right place to parse arbitrary file formats, normalise field names, and resolve ambiguous values. Instead, the parent's `process_trade_file` tool handler delegates to a sub-agent:
16
-
17
- ```ts
18
- const processTradeFile: ChatToolHandler = async (args, { requestSubAgent }) => {
19
- const result = await requestSubAgent('trade_file_extractor', {
20
- task: `Extract and normalise all trades from this file content: ${args.file_content}`,
21
- });
22
- // result is the sub-agent's final assistant message — a JSON string of normalised trades
23
- return result;
24
- };
25
- ```
26
-
27
- The sub-agent runs its own tool loop — calling `parse_csv`, `resolve_instrument`, `validate_trade_fields` etc. — and when it finishes, its final message is handed back to the parent handler. The parent then proceeds to book each trade with its own tools.
28
-
29
- Other natural candidates in the trades domain:
30
- - **Risk Judge sub-agent** — called inside a `confirm_trade` handler to validate against risk limits before confirming. The same sub-agent can be reused across multiple handlers with different `task` descriptions and `context` payloads.
31
- - **Counterparty Resolver sub-agent** — called inside a search handler to resolve raw counterparty names to canonical IDs. Multiple handlers can call it in parallel if they are invoked in the same LLM turn.
9
+ The goal is to improve quality and reduce hallucinations by routing bounded, well-defined tasks — data extraction, validation, enrichment, judge passes — to purpose-built agents with focused prompts and tight tool sets, without burdening the parent agent's context window.
32
10
 
33
11
  ---
34
12
 
35
- ## Config
13
+ ## Declaring sub-agents
36
14
 
37
- Sub-agents are declared on the parent `AgentConfig` using a new optional `subAgents` field. They use the identical `AgentConfig` shape — no new interface is needed. The `name` field is used to look up the sub-agent at call time.
15
+ Sub-agents are declared on the parent `AgentConfig` using the `subAgents` field, which takes an array of `AgentConfig` objects. The `name` field is used to look up the sub-agent at call time.
38
16
 
39
17
  ```ts
40
- // config.ts (BaseAgentConfig addition)
41
- interface BaseAgentConfig {
42
- // ... existing fields ...
18
+ import { defineAgent } from '@genesislcap/ai-assistant';
43
19
 
44
- /**
45
- * Sub-agents available to this agent's tool handlers via `requestSubAgent`.
46
- * Sub-agents are leaf nodes — their own `subAgents` field is ignored.
47
- */
48
- subAgents?: AgentConfig[];
49
-
50
- /**
51
- * When this config is used as a sub-agent, caps the number of parent history
52
- * messages passed as context. If unset, full history is passed. Messages within
53
- * the cap are passed verbatim; messages older than the cap have tool args and
54
- * results masked (same transform used by OrchestratingDriver for cross-agent history).
55
- */
56
- historyCap?: number;
57
- }
58
- ```
59
-
60
- Example declaration in the host app:
61
-
62
- ```ts
63
- const tradesAgent: SpecialistAgentConfig = {
64
- name: 'Trades Agent',
65
- description: 'Books, searches, and manages trades',
20
+ const tradesAgent = defineAgent({
21
+ name: 'Trades Assistant',
22
+ description: 'Books, searches, and manages trades.',
66
23
  systemPrompt: '...',
67
24
  toolDefinitions: [...tradeTools],
68
25
  toolHandlers: { ...tradeHandlers },
69
26
  subAgents: [
70
27
  {
71
28
  name: 'trade_file_extractor',
72
- description: 'Extracts and normalises trade data from uploaded files',
73
- historyCap: 4,
29
+ description: 'Extracts and normalises trade data from uploaded files.',
74
30
  systemPrompt: `You are a data extraction specialist. Your only job is to parse the
75
- provided file content and return a JSON array of normalised trade objects.
76
- Each trade must have: instrument, quantity, side, price, counterparty.
77
- Do not book trades. Do not ask the user questions. Just extract.`,
78
- toolDefinitions: [parseCsvDefinition, resolveInstrumentDefinition],
79
- toolHandlers: { parse_csv: parseCsvHandler, resolve_instrument: resolveInstrumentHandler },
80
- },
81
- {
82
- name: 'risk_judge',
83
- description: 'Validates a proposed action against risk limits',
84
- systemPrompt: `You are a risk validation specialist. Assess the provided action
85
- against the supplied risk limits and return a JSON object with approved (boolean)
86
- and reason (string).`,
87
- toolDefinitions: [getRiskLimitsDefinition],
88
- toolHandlers: { get_risk_limits: getRiskLimitsHandler },
31
+ provided file and return structured trade rows.
32
+ Do not book trades. Do not ask questions. Just extract.`,
33
+ toolDefinitions: [returnExtractedTradesDefinition],
34
+ toolHandlers: { return_extracted_trades: returnExtractedTradesHandler },
89
35
  },
90
36
  ],
91
- };
37
+ });
38
+ ```
39
+
40
+ ### `defineAgent` helper
41
+
42
+ Use `defineAgent` when you need the config's `name` type preserved as a string literal — required for typed `requestSubAgent` calls and `ChatToolHandlers` generic wiring (see [TypeScript types](#typescript-types)):
43
+
44
+ ```ts
45
+ import { defineAgent } from '@genesislcap/ai-assistant';
46
+
47
+ export const tradeFileExtractorAgent = defineAgent({
48
+ name: 'trade_file_extractor',
49
+ // ...
50
+ });
92
51
  ```
93
52
 
53
+ Without `defineAgent`, TypeScript widens `name` to `string`, which loses the type safety on `requestSubAgent`'s name parameter.
54
+
94
55
  ---
95
56
 
96
- ## Invocation API
57
+ ## Invoking a sub-agent
97
58
 
98
- Sub-agents are invoked from within tool handlers via `requestSubAgent`, available on the handler context alongside `requestInteraction`:
59
+ Tool handlers receive `requestSubAgent` on their context object alongside `requestInteraction`:
99
60
 
100
61
  ```ts
101
- // ChatToolHandlers context (extended)
102
- context: {
103
- requestInteraction: <T>(componentName: string, data: any) => Promise<T>,
104
- requestSubAgent: <T = never>(name: string, options?: SubAgentRequestOptions) => Promise<T | string>,
62
+ const processTradeFile = async (args, context) => {
63
+ const { file_name } = args as { file_name: string };
64
+
65
+ const result = await context.requestSubAgent('trade_file_extractor', {
66
+ task: `Extract all trade rows from the attached file named "${file_name}".`,
67
+ });
68
+
69
+ // result is either the structured value from completeSubAgent, or the
70
+ // sub-agent's final assistant message text as a string fallback.
71
+ return result;
72
+ };
73
+ ```
74
+
75
+ `requestSubAgent` is only present on the context when `subAgents` is declared on the parent agent config. Guard for its existence in reusable handlers:
76
+
77
+ ```ts
78
+ if (!context.requestSubAgent) {
79
+ return { error: 'Sub-agent support is not available in this context.' };
105
80
  }
81
+ ```
106
82
 
83
+ ### `SubAgentRequestOptions`
84
+
85
+ ```ts
107
86
  interface SubAgentRequestOptions {
108
- /** Plain-language description of what this specific invocation should do. */
87
+ /** Plain-language task description for this specific invocation. */
109
88
  task?: string;
110
- /** Per-call override of the config-level historyCap. */
89
+
90
+ /**
91
+ * Caps how many parent history messages the sub-agent receives.
92
+ * Messages within the cap are passed verbatim; older messages have
93
+ * their tool call args and results masked. Overrides any config-level
94
+ * historyCap for this call.
95
+ */
111
96
  historyCap?: number;
97
+
112
98
  /**
113
- * Extra dynamic data for this invocation. Injected as a primer message before
114
- * the task so the sub-agent sees it as part of its context.
99
+ * Extra dynamic data for this invocation. Injected as a primer message
100
+ * immediately before the task, so the sub-agent sees it naturally as
101
+ * part of its context.
115
102
  */
116
103
  context?: Record<string, unknown>;
117
104
  }
118
105
  ```
119
106
 
120
- The return type is `T | string`:
121
- - `T` — when the sub-agent explicitly calls `completeSubAgent(result)` from within one of its tool handlers. The result is the structured value passed to `completeSubAgent`, fully typed.
122
- - `string` — when the sub-agent finishes its tool loop naturally with a plain text response, without calling `completeSubAgent`. This is always valid and requires no special tooling.
123
-
124
- With `T` defaulting to `never`, `T | string` collapses to `string` when no type parameter is provided — so untyped callers get a plain string with no extra ceremony.
125
-
126
- The `name` must match a declared sub-agent on the parent config — validated at call time with a clear error if not found.
127
-
128
- ### `completeSubAgent`
129
-
130
- Sub-agent tool handlers receive `completeSubAgent` on their context. Calling it signals explicit completion with a structured result:
107
+ ---
131
108
 
132
- ```ts
133
- // Sub-agent tool handler context
134
- context: {
135
- requestInteraction: <T>(componentName: string, data: any) => Promise<T>,
136
- completeSubAgent: (result: unknown) => void,
137
- }
138
- ```
109
+ ## Returning structured results: `completeSubAgent`
139
110
 
140
- Example a judge sub-agent with an explicit completion tool:
111
+ By default, `requestSubAgent` returns the sub-agent's final assistant message text as a `string`. For structured data, define a completion tool on the sub-agent and call `completeSubAgent` from its handler:
141
112
 
142
113
  ```ts
143
- toolHandlers: {
144
- submit_judgment: async (args, { completeSubAgent }) => {
145
- completeSubAgent({ approved: args.approved, feedback: args.feedback });
146
- return 'Judgment submitted.'; // still appended to the trace for debug visibility
114
+ const toolHandlers: ChatToolHandlers = {
115
+ return_extracted_trades: async (args, context) => {
116
+ const { rows } = args as ExtractedTrades;
117
+ context.completeSubAgent?.({ rows });
118
+ return `Extracted ${rows.length} trade row(s).`; // still appended to trace
147
119
  },
148
- get_risk_limits: async (args) => {
149
- // normal tool — no completeSubAgent call needed
150
- return fetchRiskLimits();
151
- },
152
- }
120
+ };
153
121
  ```
154
122
 
155
- When `completeSubAgent` is called, `SubAgentRunner` stores the result, and after that tool iteration completes the loop exits and resolves `requestSubAgent` with the stored value. If no tool ever calls `completeSubAgent`, the runner falls back to the final assistant message text.
123
+ When `completeSubAgent` is called, the sub-agent's tool loop exits and `requestSubAgent` resolves with the value passed to `completeSubAgent`. If `completeSubAgent` is never called, the sub-agent runs to natural completion and returns its final text.
156
124
 
157
- The parent handler then gets fully typed structured data with no `JSON.parse`:
125
+ The return type of `requestSubAgent<T>` is `T | string`:
126
+ - `T` — when the sub-agent called `completeSubAgent(result)`. Fully typed, no `JSON.parse`.
127
+ - `string` — when the sub-agent finished naturally without calling `completeSubAgent`.
128
+
129
+ With `T` defaulting to `never`, `T | string` collapses to `string` when no type parameter is provided — untyped callers get a plain string with no extra ceremony.
158
130
 
159
131
  ```ts
160
- const result = await requestSubAgent<{ approved: boolean; feedback: string }>('risk_judge', {
161
- task: `Validate this trade: ${JSON.stringify(args.trade)}`,
162
- context: { limits: getCurrentRiskLimits() },
132
+ const result = await context.requestSubAgent<ExtractedTrades>('trade_file_extractor', {
133
+ task: `Extract all trades from "${file_name}".`,
163
134
  });
164
135
 
165
136
  if (typeof result === 'string') {
166
- // sub-agent returned plain text — handle gracefully
137
+ // handle gracefully — sub-agent returned plain text
167
138
  } else {
168
- const { approved, feedback } = result;
139
+ const { rows } = result; // fully typed
169
140
  }
170
141
  ```
171
142
 
172
143
  ---
173
144
 
174
- ## Runtime Mechanics
175
-
176
- ### SubAgentRunner
145
+ ## TypeScript types
177
146
 
178
- A lightweight class that owns a sub-agent's execution. Creates its own private `ChatDriver` instance, completely isolated from the parent's history.
147
+ `ChatToolHandlers` accepts an optional type parameter that narrows the `name` argument of `requestSubAgent` to the declared sub-agent names. Pass the agent config type via `typeof`:
179
148
 
180
- ```
181
- SubAgentRunner
182
- ├── Creates its own ChatDriver instance (sub-agent config)
183
- ├── Builds the history snapshot from the parent (applying historyCap + masking if set)
184
- ├── Prepends any call-time context as a primer message
185
- ├── Sends the task string as the initial user message
186
- └── Runs the tool loop to completion → returns the final assistant message text + trace
187
- ```
188
-
189
- The parent's `ChatDriver` never sees the sub-agent's intermediate messages. It only receives the final string result when the handler's promise resolves — exactly as for any other tool.
149
+ ```ts
150
+ import { defineAgent } from '@genesislcap/ai-assistant';
151
+ import type { ChatToolHandlers } from '@genesislcap/foundation-ai';
190
152
 
191
- ### Parallel execution
153
+ const tradeFileExtractorAgent = defineAgent({
154
+ name: 'trade_file_extractor',
155
+ // ...
156
+ });
192
157
 
193
- No special work needed. The parent's tool loop already runs all tool calls from a single LLM turn in parallel via `Promise.all`. If two tool handlers both call `requestSubAgent`, their `SubAgentRunner` instances start concurrently and the parent resumes after the last one resolves.
158
+ // requestSubAgent name param is now typed as 'trade_file_extractor'
159
+ const handlers: ChatToolHandlers<typeof tradeFileExtractorAgent> = {
160
+ process_trade_file: async (args, context) => {
161
+ // context.requestSubAgent('trade_file_extractor', ...) — type-checked
162
+ // context.requestSubAgent('typo', ...) — TypeScript error
163
+ },
164
+ };
165
+ ```
194
166
 
195
- ### History passed to the sub-agent
167
+ For multiple sub-agents use a union:
196
168
 
197
- By default the sub-agent receives a read-only snapshot of the full parent history as primer context. With `historyCap: N`:
198
- - The last N messages are passed verbatim with full fidelity.
199
- - Messages older than N have tool call args and results masked — using the same `transformHistoryForAgent` function already in `OrchestratingDriver`. No new logic needed.
169
+ ```ts
170
+ type Handlers = ChatToolHandlers<typeof agentA | typeof agentB>;
171
+ ```
200
172
 
201
- Any `context` object passed at call time is injected as an additional primer message immediately before the task, so the sub-agent sees it naturally as part of the conversation.
173
+ The default (`{ name: string }`) leaves the name as `string` no breaking change for existing handlers.
202
174
 
203
175
  ---
204
176
 
205
- ## History & Debug
206
-
207
- All sub-agent activity is hidden behind the existing `showToolCalls` toggle — no new UI settings needed. Sub-agents are an implementation detail of a tool call and are treated as such.
208
-
209
- ### What the parent sees (always)
177
+ ## History passed to the sub-agent
210
178
 
211
- A single `tool` role message containing the sub-agent's result. Compact, no noise.
179
+ By default the sub-agent receives a snapshot of the full parent conversation history as context. The in-flight assistant message (the one containing the tool call that triggered the sub-agent) is excluded it has unresolved tool calls that would confuse the sub-agent.
212
180
 
213
- ```
214
- [tool] process_trade_file → '[{"instrument":"AAPL","qty":100,...}, ...]'
215
- ```
216
-
217
- ### What the debug view sees (`showToolCalls: true`)
218
-
219
- The `tool` message carries an additional `subAgentTrace` field (UI-only, stripped from provider calls — same pattern as `foldPath`/`foldEvent`):
181
+ Use `historyCap` to limit how much history the sub-agent sees:
220
182
 
221
183
  ```ts
222
- // Extension to ChatToolResult
223
- interface ChatToolResult {
224
- toolCallId: string;
225
- content: string;
226
- /** Full message history from the sub-agent run, for debug display only. */
227
- subAgentTrace?: ChatMessage[];
228
- }
184
+ subAgents: [
185
+ {
186
+ name: 'trade_file_extractor',
187
+ historyCap: 4, // pass only the last 4 messages verbatim; mask older ones
188
+ // ...
189
+ },
190
+ ],
229
191
  ```
230
192
 
231
- The trace is rendered as a nested, collapsible conversation inside the parent tool call. It is breadcrumbed with the sub-agent name — consistent with how `foldPath` labels tool calls inside folds:
193
+ Or override per call:
232
194
 
233
- ```
234
- process_trade_file
235
- Trades Agent › trade_file_extractor
236
- ├── [assistant] I'll parse the file and extract the trade data.
237
- ├── [tool call] parse_csv(...)
238
- ├── [tool result] ...
239
- └── [complete] { trades: [...], warnings: [...] }
195
+ ```ts
196
+ await context.requestSubAgent('trade_file_extractor', {
197
+ historyCap: 2,
198
+ task: '...',
199
+ });
240
200
  ```
241
201
 
242
- The `agentName` field is already set on every message by `ChatDriver.appendToHistory` — the UI uses it as the breadcrumb label without any additional tagging.
243
-
244
- ### Live activity indicator
245
-
246
- While a sub-agent is running, `SubAgentRunner` fires `sub-agent-start` / `sub-agent-stop` events (with `detail: { name: string }`) via an `onProgress` callback provided by the parent `ChatDriver`. The parent dispatches these on itself so `FoundationAiAssistant` can show a contextual activity label (e.g. "Consulting risk judge...") in the same overlay used for the orchestrating classifier. Hidden behind `showToolCalls: false` by default — visible only when tool calls are shown.
202
+ Messages older than the cap have their tool call args and results replaced with a placeholder — the same masking used by the orchestrating driver for cross-agent history.
247
203
 
248
204
  ---
249
205
 
250
- ## State Sharing
206
+ ## Parallel execution
251
207
 
252
- **Structured result via `completeSubAgent` (preferred)**
208
+ No special work is required. The parent's tool loop runs all tool calls from a single LLM turn in parallel. If two tool handlers both call `requestSubAgent`, their executions start concurrently and the parent resumes after the last one resolves.
253
209
 
254
- Define an explicit completion tool on the sub-agent. When called, `completeSubAgent` resolves the `requestSubAgent` promise with a typed object — no `JSON.parse`, no brittle string parsing.
210
+ ---
255
211
 
256
- **Plain text fallback**
212
+ ## Live activity indicator
257
213
 
258
- If no tool calls `completeSubAgent`, the sub-agent's final assistant message text is returned as a `string`. Sufficient for simple cases where the sub-agent's response is human-readable prose the parent just forwards on.
214
+ While a sub-agent is running, `FoundationAiAssistant` exposes two reactive properties you can bind in the host application:
259
215
 
260
- **Shared store via tools (for side-effects)**
216
+ | Property | Type | Description |
217
+ |---|---|---|
218
+ | `liveSubAgentName` | `string \| null` | Name of the currently-running sub-agent, or `null` when idle. |
219
+ | `liveSubAgentTrace` | `ChatMessage[]` | Growing history from the sub-agent's active tool loop. Cleared on stop. |
261
220
 
262
- A sub-agent that needs to write to shared application state can be given tools that do so directly. The parent reads that state via its own tools. No built-in bridge needed this is normal tool use, wired up by the host app.
221
+ These update in real time as the sub-agent produces messages, letting the host app show a live indicator (e.g. "Consulting trade extractor...") or stream the sub-agent's intermediate steps. The built-in template renders a `live-sub-agent-trace` panel while a sub-agent is active and hides it when it stops.
263
222
 
264
223
  ---
265
224
 
266
- ## Implementation Plan
225
+ ## Debug trace
267
226
 
268
- ### 1. Config (`config.ts`)
269
- - Add `subAgents?: AgentConfig[]` and `historyCap?: number` to `BaseAgentConfig`.
227
+ When tool calls are visible (`showToolCalls: true`), each tool call that involved a sub-agent displays a collapsed trace panel. The trace shows the sub-agent's full message history — assistant turns, tool calls, and tool results — nested inside the parent tool call card, labelled with the sub-agent name.
270
228
 
271
- ### 2. Types (`chat.types.ts`)
272
- - Add `SubAgentRequestOptions` interface.
273
- - Extend `ChatToolHandlers` handler context with `requestSubAgent` and `completeSubAgent`.
229
+ The trace is stored on `ChatToolCall.subAgentTrace` (UI-only — never sent to the AI provider, same as `foldPath`).
274
230
 
275
- ### 3. SubAgentRunner (`sub-agent-runner.ts`)
276
- New class, lives alongside `ChatDriver`:
277
- - Constructor: `(aiProvider, subAgentConfig, parentHistory: readonly ChatMessage[], onProgress: (event: 'start' | 'stop', name: string) => void)`
278
- - Method: `run(task: string, options?: SubAgentRequestOptions): Promise<{ result: unknown | string; trace: ChatMessage[] }>`
279
- - Applies `historyCap` masking, prepends `context` as a primer message, creates a `ChatDriver`.
280
- - Fires `onProgress('start', name)` before `sendMessage` and `onProgress('stop', name)` when the loop completes.
281
- - Provides `completeSubAgent` in the handler context. When called, stores the result and sets a completion flag.
282
- - After each tool iteration, checks the flag — if set, exits the loop and resolves with the stored value.
283
- - If the loop ends without `completeSubAgent` being called, resolves with the final assistant message text.
284
- - Ignores `subAgents` on the sub-agent config and logs a warning if present (leaf-only, v1).
285
-
286
- ### 4. ChatDriver — `requestSubAgent` in handler context
287
- - In `applyAgent`, store `config.subAgents` as a map keyed by name.
288
- - When building the handler context object (currently `{ requestInteraction }`), add `requestSubAgent`: looks up the sub-agent config by name, constructs a `SubAgentRunner` with an `onProgress` callback that dispatches `sub-agent-start` / `sub-agent-stop` `CustomEvent`s on the `ChatDriver` itself, calls `runner.run(task, options)`, and returns the result.
289
- - If the sub-agent name is not found in the declared sub-agents, throws a descriptive error listing available names. This is caught by the existing tool error handler in `runToolLoop` and returned to the LLM as a tool result string — no special error path needed.
231
+ ---
290
232
 
291
- ### 5. `ChatToolResult` — trace field (`chat.types.ts`)
292
- - Add `subAgentTrace?: ChatMessage[]` to `ChatToolResult`.
293
- - In `ChatDriver.runToolLoop`, after a handler resolves, attach the trace if present.
294
- - Strip `subAgentTrace` in `providerHistoryTransform` so it is never sent to the AI provider.
233
+ ## Complete example
295
234
 
296
- ### 6. Debug UI
297
- - In the tool-calls panel, detect `toolResult.subAgentTrace` and render a collapsible nested conversation.
298
- - Style it distinctly (indented, labelled with the sub-agent name).
235
+ The showcase app contains a working end-to-end example:
299
236
 
300
- ---
237
+ - **Sub-agent config:** `packages/showcase/client-app/client/src/routes/ai/trades-assistant/sub-agents/trade-file-extractor.ts`
238
+ - **Parent tool handler:** `process_trade_file` in `packages/showcase/client-app/client/src/routes/ai/trades-assistant/tools/trades-tools.ts`
239
+ - **Parent agent wiring:** `packages/showcase/client-app/client/src/routes/ai/genesis-assistant/genesis-assistant.ts`
301
240
 
302
- ## Resolved Questions
241
+ The sub-agent (`trade_file_extractor`) defines a single `return_extracted_trades` tool that calls `completeSubAgent` with the parsed rows. The parent's `process_trade_file` handler calls `requestSubAgent<ExtractedTrades>('trade_file_extractor', { task: ... })` and receives the typed `ExtractedTrades` result.
303
242
 
304
- 1. **Sub-agent of sub-agent?** Not supported in v1. Sub-agents are leaf nodes — `SubAgentRunner` ignores `subAgents` on the sub-agent config and logs a warning. Revisit if a concrete use case emerges.
305
-
306
- 2. **Abort / cancellation**: Not needed. The driver registry PR (FUI-2495-ai-redux-store) decouples driver lifetime from DOM lifetime — a sub-agent that outlives a component disconnect simply completes against an unobserved driver. No abort mechanism required for v1.
243
+ ---
307
244
 
308
- 3. **Token budget / concurrency cap**: Deferred. A concurrency cap can be added later as a semaphore in `SubAgentRunner` without any interface changes.
245
+ ## Constraints (v1)
309
246
 
310
- 4. **History truncation**: Handled via `historyCap` on the sub-agent config (or overridden per-call). Messages within the cap are passed verbatim; older messages are masked using the existing `transformHistoryForAgent` function from `OrchestratingDriver`.
247
+ - Sub-agents are leaf nodes their own `subAgents` field is ignored. Nesting is not supported.
248
+ - Sub-agents share the same `AIProvider` instance as the parent. They each make independent LLM calls.
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@genesislcap/ai-assistant",
3
3
  "description": "Genesis AI Assistant micro-frontend",
4
- "version": "14.421.1",
4
+ "version": "14.422.0",
5
5
  "license": "SEE LICENSE IN license.txt",
6
6
  "main": "dist/esm/index.js",
7
7
  "types": "dist/ai-assistant.d.ts",
@@ -64,24 +64,24 @@
64
64
  }
65
65
  },
66
66
  "devDependencies": {
67
- "@genesislcap/foundation-testing": "14.421.1",
68
- "@genesislcap/genx": "14.421.1",
69
- "@genesislcap/rollup-builder": "14.421.1",
70
- "@genesislcap/ts-builder": "14.421.1",
71
- "@genesislcap/uvu-playwright-builder": "14.421.1",
72
- "@genesislcap/vite-builder": "14.421.1",
73
- "@genesislcap/webpack-builder": "14.421.1",
67
+ "@genesislcap/foundation-testing": "14.422.0",
68
+ "@genesislcap/genx": "14.422.0",
69
+ "@genesislcap/rollup-builder": "14.422.0",
70
+ "@genesislcap/ts-builder": "14.422.0",
71
+ "@genesislcap/uvu-playwright-builder": "14.422.0",
72
+ "@genesislcap/vite-builder": "14.422.0",
73
+ "@genesislcap/webpack-builder": "14.422.0",
74
74
  "@types/dompurify": "^3.0.5",
75
75
  "@types/marked": "^5.0.2"
76
76
  },
77
77
  "dependencies": {
78
- "@genesislcap/foundation-ai": "14.421.1",
79
- "@genesislcap/foundation-logger": "14.421.1",
80
- "@genesislcap/foundation-redux": "14.421.1",
81
- "@genesislcap/foundation-ui": "14.421.1",
82
- "@genesislcap/foundation-utils": "14.421.1",
83
- "@genesislcap/rapid-design-system": "14.421.1",
84
- "@genesislcap/web-core": "14.421.1",
78
+ "@genesislcap/foundation-ai": "14.422.0",
79
+ "@genesislcap/foundation-logger": "14.422.0",
80
+ "@genesislcap/foundation-redux": "14.422.0",
81
+ "@genesislcap/foundation-ui": "14.422.0",
82
+ "@genesislcap/foundation-utils": "14.422.0",
83
+ "@genesislcap/rapid-design-system": "14.422.0",
84
+ "@genesislcap/web-core": "14.422.0",
85
85
  "dompurify": "^3.3.1",
86
86
  "marked": "^17.0.3"
87
87
  },
@@ -96,5 +96,5 @@
96
96
  "peerDependencies": {
97
97
  "@microsoft/fast-react-wrapper": ">=0.3.0"
98
98
  },
99
- "gitHead": "ef3efc54221d1c0af5c842f0b98225ca813b4042"
99
+ "gitHead": "d596005408a80737f514e2d49b50c9dcc34b6913"
100
100
  }