@axlsdk/axl 0.5.0 → 0.7.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.
package/README.md CHANGED
@@ -1,6 +1,8 @@
1
- # axl
1
+ # @axlsdk/axl
2
2
 
3
- Core SDK for orchestrating agentic systems in TypeScript.
3
+ [![npm version](https://img.shields.io/npm/v/@axlsdk/axl)](https://www.npmjs.com/package/@axlsdk/axl)
4
+
5
+ Core SDK for orchestrating agentic systems in TypeScript. Part of the [Axl](https://github.com/axl-sdk/axl) monorepo.
4
6
 
5
7
  ## Installation
6
8
 
@@ -8,6 +10,120 @@ Core SDK for orchestrating agentic systems in TypeScript.
8
10
  npm install @axlsdk/axl zod
9
11
  ```
10
12
 
13
+ ## Project Structure
14
+
15
+ The recommended pattern separates config, tools, agents, workflows, and runtime into their own modules. Dependencies flow one direction: tools → agents → workflows → runtime.
16
+
17
+ ```
18
+ src/
19
+ config.ts — defineConfig (providers, state, trace)
20
+ runtime.ts — creates AxlRuntime, registers everything
21
+
22
+ tools/
23
+ db.ts — tool wrapping database queries
24
+ email.ts — tool wrapping email service
25
+
26
+ agents/
27
+ support.ts — support agent (imports its tools)
28
+ billing.ts — billing agent
29
+
30
+ workflows/
31
+ handle-ticket.ts — orchestrates support + billing agents
32
+
33
+ axl.config.ts — re-exports runtime for Axl Studio
34
+ ```
35
+
36
+ ### Config
37
+
38
+ Use `defineConfig` to create a typed configuration. Keep this separate from your runtime so you can swap configs per environment:
39
+
40
+ ```typescript
41
+ // src/config.ts
42
+ import { defineConfig } from '@axlsdk/axl';
43
+
44
+ export const config = defineConfig({
45
+ providers: {
46
+ openai: { apiKey: process.env.OPENAI_API_KEY },
47
+ anthropic: { apiKey: process.env.ANTHROPIC_API_KEY },
48
+ google: { apiKey: process.env.GOOGLE_API_KEY },
49
+ },
50
+ state: { store: 'sqlite', sqlite: { path: './data/axl.db' } },
51
+ trace: { enabled: true, level: 'steps' },
52
+ });
53
+ ```
54
+
55
+ Provider API keys are also read automatically from environment variables (`OPENAI_API_KEY`, `ANTHROPIC_API_KEY`, `GOOGLE_API_KEY`/`GEMINI_API_KEY`), so for local development you can skip the `providers` block entirely.
56
+
57
+ State store options: `'memory'` (default), `'sqlite'` (requires `better-sqlite3`), or a `RedisStore` instance for multi-process deployments. See [State Stores](#state-stores).
58
+
59
+ ### Tools, Agents, and Workflows
60
+
61
+ Define each in its own module. Tools wrap your services, agents import the tools they need, workflows orchestrate agents:
62
+
63
+ ```typescript
64
+ // src/tools/db.ts
65
+ import { tool } from '@axlsdk/axl';
66
+ import { z } from 'zod';
67
+ import { db } from '../services/db.js';
68
+
69
+ export const lookupOrder = tool({
70
+ name: 'lookup_order',
71
+ description: 'Look up an order by ID',
72
+ input: z.object({ orderId: z.string() }),
73
+ handler: async ({ orderId }) => db.orders.findById(orderId),
74
+ });
75
+ ```
76
+
77
+ ```typescript
78
+ // src/agents/support.ts
79
+ import { agent } from '@axlsdk/axl';
80
+ import { lookupOrder } from '../tools/db.js';
81
+
82
+ export const supportAgent = agent({
83
+ name: 'support',
84
+ model: 'openai-responses:gpt-5.4',
85
+ system: 'You are a customer support agent. Use tools to look up order information.',
86
+ tools: [lookupOrder],
87
+ });
88
+ ```
89
+
90
+ ```typescript
91
+ // src/workflows/handle-ticket.ts
92
+ import { workflow } from '@axlsdk/axl';
93
+ import { z } from 'zod';
94
+ import { supportAgent } from '../agents/support.js';
95
+
96
+ export const handleTicket = workflow({
97
+ name: 'handle-ticket',
98
+ input: z.object({ message: z.string() }),
99
+ handler: async (ctx) => ctx.ask(supportAgent, ctx.input.message),
100
+ });
101
+ ```
102
+
103
+ ### Runtime
104
+
105
+ The runtime is the composition root — it imports the config and registers all workflows. Your application and [Axl Studio](https://github.com/axl-sdk/axl/tree/main/packages/axl-studio) both import this module:
106
+
107
+ ```typescript
108
+ // src/runtime.ts
109
+ import { AxlRuntime } from '@axlsdk/axl';
110
+ import { config } from './config.js';
111
+ import { handleTicket } from './workflows/handle-ticket.js';
112
+ import { supportAgent } from './agents/support.js';
113
+ import { lookupOrder } from './tools/db.js';
114
+
115
+ export const runtime = new AxlRuntime(config);
116
+ runtime.register(handleTicket);
117
+ runtime.registerAgent(supportAgent);
118
+ runtime.registerTool(lookupOrder);
119
+ ```
120
+
121
+ ```typescript
122
+ // axl.config.ts — thin entry point for Axl Studio
123
+ import { runtime } from './src/runtime.js';
124
+ export default runtime;
125
+ ```
126
+
11
127
  ## API
12
128
 
13
129
  ### `tool(config)`
@@ -26,11 +142,23 @@ const calculator = tool({
26
142
  const result = new Function(`return (${expression})`)();
27
143
  return { result };
28
144
  },
145
+ // handler also accepts (input, ctx) for nested agent invocations — see below
29
146
  retry: { attempts: 3, backoff: 'exponential' },
30
147
  sensitive: false,
31
148
  });
32
149
  ```
33
150
 
151
+ Tool handlers receive a second parameter `ctx: WorkflowContext` (a child context), enabling the "agent-as-tool" composition pattern:
152
+
153
+ ```typescript
154
+ const researchTool = tool({
155
+ name: 'research',
156
+ description: 'Delegate to a specialist',
157
+ input: z.object({ question: z.string() }),
158
+ handler: async (input, ctx) => ctx.ask(researcher, input.question),
159
+ });
160
+ ```
161
+
34
162
  ### `agent(config)`
35
163
 
36
164
  Define an agent with model, system prompt, tools, and handoffs:
@@ -40,10 +168,10 @@ import { agent } from '@axlsdk/axl';
40
168
 
41
169
  const researcher = agent({
42
170
  name: 'researcher',
43
- model: 'openai:gpt-4o',
171
+ model: 'openai-responses:gpt-5.4',
44
172
  system: 'You are a research assistant.',
45
173
  tools: [calculator],
46
- thinking: 'high',
174
+ effort: 'high',
47
175
  maxTurns: 10,
48
176
  timeout: '30s',
49
177
  temperature: 0.7,
@@ -55,41 +183,76 @@ Dynamic model and system prompt selection:
55
183
 
56
184
  ```typescript
57
185
  const dynamicAgent = agent({
58
- model: (ctx) => ctx.metadata?.tier === 'premium'
59
- ? 'openai:gpt-4o'
60
- : 'openai:gpt-4.1-nano',
186
+ model: (ctx) =>
187
+ ctx.metadata?.tier === 'premium'
188
+ ? 'openai-responses:gpt-5.4'
189
+ : 'openai-responses:gpt-5-nano',
61
190
  system: (ctx) => `You are a ${ctx.metadata?.role ?? 'general'} assistant.`,
62
191
  });
63
192
  ```
64
193
 
65
- #### Thinking (cross-provider reasoning control)
194
+ #### Dynamic Handoffs
195
+
196
+ `handoffs` accepts a static array or a function for runtime-conditional routing:
197
+
198
+ ```typescript
199
+ const router = agent({
200
+ model: 'openai-responses:gpt-5-mini',
201
+ system: 'Route to the right specialist.',
202
+ handoffs: (ctx) => {
203
+ const base = [
204
+ { agent: billingAgent, description: 'Billing issues' },
205
+ { agent: shippingAgent, description: 'Shipping questions' },
206
+ ];
207
+ if (ctx.metadata?.tier === 'enterprise') {
208
+ base.push({ agent: priorityAgent, description: 'Priority support' });
209
+ }
210
+ return base;
211
+ },
212
+ });
213
+ ```
214
+
215
+ #### Workflow-Level Routing with `ctx.delegate()`
216
+
217
+ When your workflow (not an agent's LLM) needs to pick the best agent:
218
+
219
+ ```typescript
220
+ const result = await ctx.delegate(
221
+ [billingAgent, shippingAgent, returnsAgent],
222
+ customerMessage,
223
+ );
224
+ ```
225
+
226
+ `ctx.delegate()` creates a temporary router agent that uses handoffs to select the best candidate. For a single agent, it calls `ctx.ask()` directly with no routing overhead.
227
+
228
+ #### Effort (cross-provider reasoning control)
66
229
 
67
- The `thinking` parameter provides a unified way to control reasoning depth across all providers:
230
+ The `effort` parameter provides a unified way to control reasoning depth across all providers:
68
231
 
69
232
  ```typescript
70
233
  // Simple levels — works on any provider
71
234
  const reasoner = agent({
72
- model: 'anthropic:claude-sonnet-4-5',
235
+ model: 'anthropic:claude-opus-4-6',
73
236
  system: 'You are a careful analyst.',
74
- thinking: 'high', // 'low' | 'medium' | 'high' | 'max'
237
+ effort: 'high', // 'none' | 'low' | 'medium' | 'high' | 'max'
75
238
  });
76
239
 
77
- // Explicit budget (in tokens)
240
+ // Explicit thinking budget (in tokens — supported on Gemini 2.x and Anthropic)
78
241
  const budgetReasoner = agent({
79
- model: 'google:gemini-2.5-flash',
242
+ model: 'google:gemini-2.5-pro',
80
243
  system: 'Think step by step.',
81
- thinking: { budgetTokens: 5000 },
244
+ thinkingBudget: 5000,
82
245
  });
83
246
 
84
247
  // Per-call override
85
- const result = await reasoner.ask('Analyze this data', { thinking: 'low' });
248
+ const result = await reasoner.ask('Analyze this data', { effort: 'low' });
86
249
  ```
87
250
 
88
- Each provider maps `thinking` to its native API: `reasoning_effort` (OpenAI), `budget_tokens` (Anthropic), `thinkingBudget` (Gemini). See [docs/providers.md](../../docs/providers.md) for the full mapping table.
251
+ Each provider maps `effort` to its native API: reasoning effort (OpenAI), adaptive thinking (Anthropic), thinking level/budget (Gemini). See [docs/providers.md](../../docs/providers.md) for the full mapping table.
89
252
 
90
253
  ### `workflow(config)`
91
254
 
92
- Define a named workflow with typed input/output:
255
+ Define a named workflow with typed input:
93
256
 
94
257
  ```typescript
95
258
  import { workflow } from '@axlsdk/axl';
@@ -98,10 +261,28 @@ import { z } from 'zod';
98
261
  const myWorkflow = workflow({
99
262
  name: 'my-workflow',
100
263
  input: z.object({ query: z.string() }),
101
- output: z.object({ answer: z.string() }),
102
264
  handler: async (ctx) => {
103
- const answer = await ctx.ask(researcher, ctx.input.query);
104
- return { answer };
265
+ return ctx.ask(researcher, ctx.input.query, {
266
+ schema: z.object({ answer: z.string() }),
267
+ });
268
+ },
269
+ });
270
+ ```
271
+
272
+ For single-ask workflows, use `schema` on `ctx.ask()` — it instructs the LLM and retries automatically on invalid output. The optional `output` field validates your handler's return value *after* it runs (no LLM retry), which is useful for multi-step workflows where your orchestration logic (spawn, vote, transform) could assemble the wrong shape:
273
+
274
+ ```typescript
275
+ const answerSchema = z.object({ answer: z.number() });
276
+
277
+ const reliable = workflow({
278
+ name: 'reliable',
279
+ input: z.object({ question: z.string() }),
280
+ output: answerSchema, // validates the spawn+vote result, not the LLM
281
+ handler: async (ctx) => {
282
+ const results = await ctx.spawn(3, async (_i) =>
283
+ ctx.ask(mathAgent, ctx.input.question, { schema: answerSchema }),
284
+ );
285
+ return ctx.vote(results, { strategy: 'majority', key: 'answer' });
105
286
  },
106
287
  });
107
288
  ```
@@ -111,9 +292,6 @@ const myWorkflow = workflow({
111
292
  Register and execute workflows:
112
293
 
113
294
  ```typescript
114
- import { AxlRuntime } from '@axlsdk/axl';
115
-
116
- const runtime = new AxlRuntime();
117
295
  runtime.register(myWorkflow);
118
296
 
119
297
  // Execute
@@ -129,7 +307,12 @@ for await (const event of stream) {
129
307
  const session = runtime.session('user-123');
130
308
  await session.send('my-workflow', { query: 'Hello' });
131
309
  await session.send('my-workflow', { query: 'Follow-up' });
132
- const history = await session.history();
310
+
311
+ // Stream a session turn
312
+ const sessionStream = await session.stream('my-workflow', { query: 'Hello' });
313
+ for await (const event of sessionStream) {
314
+ if (event.type === 'token') process.stdout.write(event.data);
315
+ }
133
316
  ```
134
317
 
135
318
  ### Context Primitives
@@ -137,32 +320,33 @@ const history = await session.history();
137
320
  All available on `ctx` inside workflow handlers. See the [API Reference](../../docs/api-reference.md) for complete option types, valid values, and defaults.
138
321
 
139
322
  ```typescript
140
- // Invoke an agent
323
+ // Invoke an agent (schema retries rebuild the call with the failed output + error in the prompt)
141
324
  const answer = await ctx.ask(agent, 'prompt', { schema, retries });
142
325
 
143
326
  // Run 3 agents in parallel — each gets the same question independently
144
327
  const results = await ctx.spawn(3, async (i) => ctx.ask(agent, prompts[i]));
145
328
 
146
- // Pick the answer that appeared most often (pure aggregation, no LLM involved)
147
- const winner = ctx.vote(results, { strategy: 'majority', key: 'answer' });
329
+ // Pick the answer that appeared most often also supports LLM-as-judge via scorer
330
+ const winner = await ctx.vote(results, { strategy: 'majority', key: 'answer' });
148
331
 
149
- // Self-correcting validation
332
+ // Generic retry-until-valid loop (not conversation-aware — you decide how to use the error)
150
333
  const valid = await ctx.verify(
151
- async (lastOutput, error) => ctx.ask(agent, prompt),
334
+ async (lastOutput, error) => ctx.ask(agent, error ? `Fix: ${error}` : prompt),
152
335
  schema,
153
336
  { retries: 3, fallback: defaultValue },
154
337
  );
155
338
 
156
- // Cost control
157
- const budgeted = await ctx.budget({ cost: '$1.00', onExceed: 'hard_stop' }, async () => {
158
- return ctx.ask(agent, prompt);
159
- });
339
+ // Cost control — returns { value, budgetExceeded, totalCost }
340
+ const { value } = await ctx.budget(
341
+ { cost: '$1.00', onExceed: 'hard_stop' },
342
+ async () => ctx.ask(agent, prompt),
343
+ );
160
344
 
161
345
  // First to complete
162
- const fastest = await ctx.race([
163
- () => ctx.ask(agentA, prompt),
164
- () => ctx.ask(agentB, prompt),
165
- ], { schema });
346
+ const fastest = await ctx.race(
347
+ [() => ctx.ask(agentA, prompt), () => ctx.ask(agentB, prompt)],
348
+ { schema },
349
+ );
166
350
 
167
351
  // Concurrent independent tasks
168
352
  const [a, b] = await ctx.parallel([
@@ -176,14 +360,16 @@ const mapped = await ctx.map(items, async (item) => ctx.ask(agent, item), {
176
360
  quorum: 3,
177
361
  });
178
362
 
179
- // Human-in-the-loop
363
+ // Human-in-the-loop — suspends until resolved via API or Studio
180
364
  const decision = await ctx.awaitHuman({
181
- channel: 'slack',
365
+ channel: 'approvals',
182
366
  prompt: 'Approve this action?',
183
367
  });
184
368
 
185
- // Durable checkpoint
186
- const value = await ctx.checkpoint(async () => expensiveOperation());
369
+ // Durable checkpoint — on first run, executes and saves the result.
370
+ // On replay after a restart, returns the saved result without re-executing,
371
+ // preventing duplicate side effects (double API calls, double charges, etc.)
372
+ const checkpointed = await ctx.checkpoint(async () => expensiveOperation());
187
373
  ```
188
374
 
189
375
  ### OpenTelemetry Observability
@@ -192,13 +378,18 @@ Automatic span emission for every `ctx.*` primitive with cost-per-span attributi
192
378
 
193
379
  ```typescript
194
380
  import { defineConfig, AxlRuntime } from '@axlsdk/axl';
195
- import { BasicTracerProvider, SimpleSpanProcessor } from '@opentelemetry/sdk-trace-base';
381
+ import {
382
+ BasicTracerProvider,
383
+ SimpleSpanProcessor,
384
+ } from '@opentelemetry/sdk-trace-base';
196
385
  import { OTLPTraceExporter } from '@opentelemetry/exporter-trace-otlp-http';
197
386
 
198
387
  const tracerProvider = new BasicTracerProvider();
199
- tracerProvider.addSpanProcessor(new SimpleSpanProcessor(
200
- new OTLPTraceExporter({ url: 'http://localhost:4318/v1/traces' }),
201
- ));
388
+ tracerProvider.addSpanProcessor(
389
+ new SimpleSpanProcessor(
390
+ new OTLPTraceExporter({ url: 'http://localhost:4318/v1/traces' }),
391
+ ),
392
+ );
202
393
 
203
394
  const config = defineConfig({
204
395
  telemetry: {
@@ -222,7 +413,7 @@ import { createSpanManager, NoopSpanManager } from '@axlsdk/axl';
222
413
 
223
414
  ### Memory Primitives
224
415
 
225
- Working memory backed by the existing `StateStore` interface:
416
+ Working memory backed by the `StateStore` interface:
226
417
 
227
418
  ```typescript
228
419
  // Store and retrieve structured state
@@ -235,18 +426,20 @@ await ctx.remember('user-profile', data, { scope: 'global' });
235
426
  const profile = await ctx.recall('user-profile', { scope: 'global' });
236
427
  ```
237
428
 
238
- Semantic recall requires a vector store and embedder configured on the runtime:
429
+ Semantic recall requires a vector store and embedder on the config:
239
430
 
240
431
  ```typescript
241
- import { AxlRuntime, InMemoryVectorStore, OpenAIEmbedder } from '@axlsdk/axl';
432
+ import { defineConfig, AxlRuntime, InMemoryVectorStore, OpenAIEmbedder } from '@axlsdk/axl';
242
433
 
243
- const runtime = new AxlRuntime({
434
+ const config = defineConfig({
244
435
  memory: {
245
436
  vectorStore: new InMemoryVectorStore(),
246
437
  embedder: new OpenAIEmbedder({ model: 'text-embedding-3-small' }),
247
438
  },
248
439
  });
249
440
 
441
+ const runtime = new AxlRuntime(config);
442
+
250
443
  // In a workflow:
251
444
  const relevant = await ctx.recall('knowledge-base', {
252
445
  query: 'refund policy',
@@ -266,7 +459,7 @@ const containsPII = (text: string) => /\b\d{3}-\d{2}-\d{4}\b/.test(text);
266
459
  const isOffTopic = (text: string) => !text.toLowerCase().includes('support');
267
460
 
268
461
  const safe = agent({
269
- model: 'openai:gpt-4o',
462
+ model: 'openai-responses:gpt-5.4',
270
463
  system: 'You are a helpful assistant.',
271
464
  guardrails: {
272
465
  input: async (prompt, ctx) => {
@@ -274,83 +467,108 @@ const safe = agent({
274
467
  return { block: false };
275
468
  },
276
469
  output: async (response, ctx) => {
277
- if (isOffTopic(response)) return { block: true, reason: 'Off-topic response' };
470
+ if (isOffTopic(response))
471
+ return { block: true, reason: 'Off-topic response' };
278
472
  return { block: false };
279
473
  },
280
- onBlock: 'retry', // 'retry' | 'throw' | (reason, ctx) => fallbackResponse
474
+ onBlock: 'retry', // 'retry' | 'throw' | (reason, ctx) => fallbackResponse
281
475
  maxRetries: 2,
282
476
  },
283
477
  });
284
478
  ```
285
479
 
286
- When `onBlock` is `'retry'`, the LLM sees the block reason and self-corrects (same pattern as `ctx.verify()`). Throws `GuardrailError` if retries are exhausted or `onBlock` is `'throw'`.
480
+ When `onBlock` is `'retry'`, the LLM's blocked output is appended to the conversation (as an assistant message) along with a system message containing the block reason, then the LLM is re-called so it can self-correct. These messages **accumulate** across retries — if the guardrail blocks multiple times, the LLM sees all prior failed attempts and corrections before its next try. All retry messages are ephemeral — they are **not** persisted to session history, so subsequent session turns never see the blocked attempts. Note: `ctx.ask()` schema retries work differently — each retry rebuilds the call from scratch and only includes the most recent failed output and error (previous failures do not accumulate). Input guardrails always throw since the prompt is user-supplied. Throws `GuardrailError` if retries are exhausted or `onBlock` is `'throw'`.
481
+
482
+ ### State Stores
483
+
484
+ Three built-in implementations. All persist the same data: workflow execution checkpoints, `awaitHuman` decisions, session history, memory entries, and the execution state needed for suspend/resume.
485
+
486
+ **Memory** (default) — in-process, no persistence. Use for development and stateless workflows.
487
+
488
+ ```typescript
489
+ const runtime = new AxlRuntime();
490
+ ```
491
+
492
+ **SQLite** — file-based persistence. Use for single-process deployments that need durable state across restarts.
493
+
494
+ ```bash
495
+ npm install better-sqlite3
496
+ ```
497
+
498
+ ```typescript
499
+ const runtime = new AxlRuntime({
500
+ state: { store: 'sqlite', sqlite: { path: './data/axl.db' } },
501
+ });
502
+ ```
503
+
504
+ **Redis** — shared state across multiple processes. Use for multi-replica deployments or any setup where more than one process runs `AxlRuntime`.
505
+
506
+ ```bash
507
+ npm install redis
508
+ ```
509
+
510
+ ```typescript
511
+ import { AxlRuntime, RedisStore } from '@axlsdk/axl';
512
+
513
+ const store = await RedisStore.create('redis://localhost:6379');
514
+ const runtime = new AxlRuntime({ state: { store } });
515
+
516
+ // Graceful shutdown — closes the Redis connection
517
+ await runtime.shutdown();
518
+ ```
519
+
520
+ `RedisStore.create()` connects before returning, so any connection error surfaces at startup rather than on first use. The runtime's `shutdown()` closes the connection automatically.
287
521
 
288
522
  ### Session Options
289
523
 
290
524
  ```typescript
291
525
  const session = runtime.session('user-123', {
292
526
  history: {
293
- maxMessages: 100, // Trim oldest messages when exceeded
294
- summarize: true, // Auto-summarize trimmed messages
295
- summaryModel: 'openai:gpt-4o-mini', // Model for summarization
527
+ maxMessages: 100,
528
+ summarize: true,
529
+ summaryModel: 'openai-responses:gpt-5-mini',
296
530
  },
297
- persist: true, // Save to StateStore (default: true)
531
+ persist: true,
298
532
  });
299
533
  ```
300
534
 
301
- `SessionOptions` type:
535
+ When `maxMessages` is exceeded:
536
+
537
+ - **`summarize: false`** (default) — oldest messages beyond the limit are dropped. Only the most recent `maxMessages` are kept.
538
+ - **`summarize: true`** — before dropping, the overflow messages are sent to `summaryModel` for summarization. The summary is saved to session metadata and included as context on subsequent turns. Each time the limit is exceeded again, the new overflow is summarized together with the previous summary, so context accumulates incrementally.
302
539
 
303
- | Option | Type | Default | Description |
304
- |--------|------|---------|-------------|
305
- | `history.maxMessages` | `number` | unlimited | Max messages to retain |
306
- | `history.summarize` | `boolean` | `false` | Summarize trimmed messages |
307
- | `history.summaryModel` | `string` | — | Model URI for summarization (required when `summarize: true`) |
308
- | `persist` | `boolean` | `true` | Persist history to StateStore |
540
+ | Option | Type | Default | Description |
541
+ | ---------------------- | --------- | --------- | ------------------------------------------------------------- |
542
+ | `history.maxMessages` | `number` | unlimited | Max messages to retain in history |
543
+ | `history.summarize` | `boolean` | `false` | Summarize overflow messages instead of dropping them |
544
+ | `history.summaryModel` | `string` | — | Model URI for summarization (required when `summarize: true`) |
545
+ | `persist` | `boolean` | `true` | Persist history to StateStore |
309
546
 
310
547
  ### Error Hierarchy
311
548
 
312
549
  ```typescript
313
550
  import {
314
- AxlError, // Base class
315
- VerifyError, // Schema validation failed after retries
316
- QuorumNotMet, // Quorum threshold not reached
317
- NoConsensus, // Vote could not reach consensus
318
- TimeoutError, // Operation exceeded timeout
319
- MaxTurnsError, // Agent exceeded max tool-calling turns
551
+ AxlError, // Base class
552
+ VerifyError, // Schema validation failed after retries
553
+ QuorumNotMet, // Quorum threshold not reached
554
+ NoConsensus, // Vote could not reach consensus
555
+ TimeoutError, // Operation exceeded timeout
556
+ MaxTurnsError, // Agent exceeded max tool-calling turns
320
557
  BudgetExceededError, // Budget limit exceeded
321
- GuardrailError, // Guardrail blocked input or output
322
- ToolDenied, // Agent tried to call unauthorized tool
558
+ GuardrailError, // Guardrail blocked input or output
559
+ ToolDenied, // Agent tried to call unauthorized tool
323
560
  } from '@axlsdk/axl';
324
561
  ```
325
562
 
326
- ### State Stores
327
-
328
- ```typescript
329
- import { MemoryStore, SQLiteStore, RedisStore } from '@axlsdk/axl';
330
-
331
- // In-memory (default)
332
- const runtime = new AxlRuntime();
333
-
334
- // SQLite (requires better-sqlite3)
335
- const runtime = new AxlRuntime({
336
- state: { store: 'sqlite', sqlite: { path: './data/axl.db' } },
337
- });
338
-
339
- // Redis (requires ioredis)
340
- const runtime = new AxlRuntime({
341
- state: { store: 'redis', redis: { url: 'redis://localhost:6379' } },
342
- });
343
- ```
344
-
345
563
  ### Provider URIs
346
564
 
347
565
  Four built-in providers using the `provider:model` URI scheme:
348
566
 
349
567
  ```
350
- openai:gpt-4o # OpenAI Chat Completions
351
- openai-responses:gpt-4o # OpenAI Responses API
352
- anthropic:claude-sonnet-4-5 # Anthropic
353
- google:gemini-2.5-pro # Google Gemini
568
+ openai-responses:gpt-5.4 # OpenAI Responses API (preferred over Chat Completions)
569
+ openai:gpt-5.4 # OpenAI Chat Completions
570
+ anthropic:claude-sonnet-4-6 # Anthropic
571
+ google:gemini-3.1-pro-preview # Google Gemini
354
572
  ```
355
573
 
356
574
  See [docs/providers.md](../../docs/providers.md) for the full model list including reasoning models.