lemura 1.5.5 → 1.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/CHANGELOG.md CHANGED
@@ -5,6 +5,32 @@ All notable changes to this project will be documented in this file.
5
5
  The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
6
6
  and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
7
 
8
+ ## [1.7.0] - 2026-06-13
9
+
10
+ ### Added
11
+
12
+ - **Progressive skills — model-driven skill selection** (`strategy: 'progressive'`): A third skill strategy alongside `fixed` and `dynamic`. The model sees a lightweight catalog (`name: description`) of progressive skills in its system prompt and decides which to pull in by calling a built-in `load_skill` tool; only the chosen skill's full content is then injected. This is the "progressive disclosure" pattern — the host wires nothing, the agent selects its own skills. Fully backward-compatible (default strategy remains `fixed`).
13
+ - **Built-in `load_skill` tool** (`src/tools/builtin/load_skill.ts`, `createLoadSkillTool`): Auto-registered by `SessionManager` whenever a `progressive` skill is present, and auto-trusted by the tool firewall (it only toggles skill injection, no external side effects). The `name` parameter is constrained to the available progressive skills; unknown names and `maxConcurrent` violations return soft errors that keep the ReAct loop going.
14
+ - **`SkillSelectionConfig`** (`SessionConfig.skillSelection`): Tunes progressive selection — `persistence` (`'per_turn'` default, or `'session'`), `maxConcurrent` (cap on simultaneously loaded skills), and `catalogHeader` (override the catalog preamble). Ignored when no progressive skills are present.
15
+ - **`SkillInjector.buildCatalog(header?)`**: Builds the `name: description` catalog block for progressive skills. Plus `resetProgressiveSkills()`, `getProgressiveSkills()`, and `countEnabledProgressive()` helpers. `enableSkill`/`disableSkill`/`enableByTags`/`disableByTags` now operate on both `dynamic` and `progressive` skills.
16
+ - **Full skill-lifecycle tracing**: `skill_load` now fires for **every registered skill** (not just active ones) and carries an `enabled` flag, so inactive `dynamic`/`progressive` skills are visible from session init. New `skill_enable` trace (`{ name, source: 'load_skill' }`) records the model's selection decision when it loads a progressive skill, and new `skill_reset` trace (`{ skills, reason: 'per_turn' }`) records per-turn clearing. `session_init` reports a `progressive` skill count; `skill_inject` fires for both dynamic and progressive injections.
17
+ - **Tests**: `tests/unit/skills/ProgressiveSkills.test.ts` (catalog, activation, `load_skill` tool, `maxConcurrent`) and new `SessionManager` cases (auto-registration, per-turn vs session persistence).
18
+
19
+ ## [1.6.0] - 2026-06-05
20
+
21
+ ### Added
22
+
23
+ - **MetaRouter — per-turn intent classification + tool narrowing** (`SessionConfig.enableRouting`): An optional router runs once at the start of each turn, before the ReAct loop, to (1) classify the message as `chat` or `task`, and (2) select which tool categories are relevant. Tools whose `category` is not selected are hidden from the model for that turn — fewer, more relevant tools means less confusion and lower token cost. A `chat` verdict also suppresses goal planning and verification for that turn. Off by default; when disabled, all tools are exposed exactly as before (fully backward-compatible).
24
+ - **`IRouterAdapter` interface** (`src/types/agent.ts`): `route(userMessage, availableCategories) => RouterDecision`. Supply a custom router via `SessionConfig.router`, or use the built-in `LLMRouter`. Implementations should fail safe — return `{ mode: 'task', categories: <all> }` on error so the agent never loses tool access.
25
+ - **`LLMRouter`** (`src/agent/execution/Router.ts`): Built-in router. Single temperature-0 LLM classification call, a conversational fast-path that skips the LLM for greetings/acknowledgements, and a fail-safe fallback to all categories on any parse/LLM error. Hallucinated categories not in the offered set are dropped.
26
+ - **`RouterDecision` / `ToolCategoryInfo` interfaces** (`src/types/agent.ts`): `RouterDecision` = `{ mode: 'chat' | 'task'; categories: string[]; reason?: string }`. `ToolCategoryInfo` = `{ name: string; tools: string[] }`, passed to the router so it knows what each category contains.
27
+ - **`IToolDefinition.category?: string`**: Optional category tag used by the router to group tools. Open string — lemura does not own a fixed catalog. Uncategorized tools are never filtered out (treated as always available).
28
+ - **`SessionConfig.router`**: Custom `IRouterAdapter`. Takes precedence over the built-in router when `enableRouting` is true.
29
+ - **`SessionConfig.routerModel`**: Model used by the built-in router. Defaults to `config.model` — point this at a small/cheap model for fast classification.
30
+ - **`SessionConfig.alwaysAvailableCategories?: string[]`**: Categories always exposed regardless of the router decision (e.g. a scratchpad category).
31
+ - **`TraceEvent.type: 'routing'`**: New trace type. Emitted as `route_decision` with `mode`, `categories`, and `reason`.
32
+ - **`Router.test.ts`**: Unit tests covering tool narrowing, always-available categories, the routing-disabled default, the no-categorized-tools fallback, and the built-in `LLMRouter` (fast-path, hallucinated-category dropping, fail-safe).
33
+
8
34
  ## [1.5.5] - 2026-06-01
9
35
 
10
36
  ### Fixed
package/README.md CHANGED
@@ -25,6 +25,7 @@
25
25
  ### ✨ Key Features
26
26
 
27
27
  - **🧠 Dynamic Skill Market**: Switch skills on/off at runtime via tags, names, or tool dependencies.
28
+ - **📖 Progressive Skills**: Let the agent pick its own skills — it reads a catalog and loads only what's relevant via the built-in `load_skill` tool (`strategy: 'progressive'`).
28
29
  - **🔌 Native MCP Support**: Connect to any Model Context Protocol server with custom header support (Auth).
29
30
  - **🛡️ Tool Firewall**: Fully integrated ask/accept/deny policy layer for secure tool execution — fail-safe by design.
30
31
  - **🎯 Goal Maintenance & Verification**: LLM-powered sub-goal decomposition, progress reconciliation, and post-run goal verification that re-enters the loop with full tool access to finish incomplete answers.
@@ -1,4 +1,4 @@
1
- import { a as IProviderAdapter, l as CompletionRequest, m as CompletionResponse, k as CompletionChunk, M as ModelInfo, d as TranscriptionRequest, e as TranscriptionResponse, f as SynthesisRequest, A as AudioChunk, V as VisionRequest, g as VisionResponse, h as ImageGenRequest, i as ImageGenResponse } from '../adapters-DAzmrg4l.mjs';
1
+ import { a as IProviderAdapter, l as CompletionRequest, m as CompletionResponse, k as CompletionChunk, M as ModelInfo, d as TranscriptionRequest, e as TranscriptionResponse, f as SynthesisRequest, A as AudioChunk, V as VisionRequest, g as VisionResponse, h as ImageGenRequest, i as ImageGenResponse } from '../adapters-BN6und82.mjs';
2
2
  import '../rag-La_Bo-J8.mjs';
3
3
  import '../logger-DxvKliuk.mjs';
4
4
 
@@ -1,4 +1,4 @@
1
- import { a as IProviderAdapter, l as CompletionRequest, m as CompletionResponse, k as CompletionChunk, M as ModelInfo, d as TranscriptionRequest, e as TranscriptionResponse, f as SynthesisRequest, A as AudioChunk, V as VisionRequest, g as VisionResponse, h as ImageGenRequest, i as ImageGenResponse } from '../adapters-CIRkrCHl.js';
1
+ import { a as IProviderAdapter, l as CompletionRequest, m as CompletionResponse, k as CompletionChunk, M as ModelInfo, d as TranscriptionRequest, e as TranscriptionResponse, f as SynthesisRequest, A as AudioChunk, V as VisionRequest, g as VisionResponse, h as ImageGenRequest, i as ImageGenResponse } from '../adapters-CnsgefYR.js';
2
2
  import '../rag-La_Bo-J8.js';
3
3
  import '../logger-DxvKliuk.js';
4
4
 
@@ -151,6 +151,15 @@ interface IToolDefinition {
151
151
  parameters: Record<string, unknown>;
152
152
  /** Per-call timeout in milliseconds. Falls back to ToolRegistry.defaultTimeoutMs when omitted. */
153
153
  timeoutMs?: number;
154
+ /**
155
+ * Optional category used by the router (see `SessionConfig.enableRouting`) to
156
+ * narrow which tools are exposed to the model on a given turn. Open string —
157
+ * lemura does not own a fixed catalog. Tools left uncategorized are never
158
+ * filtered out (treated as always available).
159
+ *
160
+ * @since 1.6.0
161
+ */
162
+ category?: string;
154
163
  execute(params: unknown, context: ToolContext): Promise<unknown>;
155
164
  }
156
165
 
@@ -151,6 +151,15 @@ interface IToolDefinition {
151
151
  parameters: Record<string, unknown>;
152
152
  /** Per-call timeout in milliseconds. Falls back to ToolRegistry.defaultTimeoutMs when omitted. */
153
153
  timeoutMs?: number;
154
+ /**
155
+ * Optional category used by the router (see `SessionConfig.enableRouting`) to
156
+ * narrow which tools are exposed to the model on a given turn. Open string —
157
+ * lemura does not own a fixed catalog. Tools left uncategorized are never
158
+ * filtered out (treated as always available).
159
+ *
160
+ * @since 1.6.0
161
+ */
162
+ category?: string;
154
163
  execute(params: unknown, context: ToolContext): Promise<unknown>;
155
164
  }
156
165
 
@@ -1,6 +1,6 @@
1
- import { I as IToolDefinition, a as IProviderAdapter, b as IContextStrategy, S as ShortTermMemoryRegistry, c as IScratchpadAdapter, T as Turn } from './adapters-CIRkrCHl.js';
1
+ import { I as IToolDefinition, a as IProviderAdapter, b as IContextStrategy, S as ShortTermMemoryRegistry, c as IScratchpadAdapter, T as Turn } from './adapters-CnsgefYR.js';
2
2
  import { I as ILogger } from './logger-DxvKliuk.js';
3
- import { I as ISkill } from './skills-Y6D7zSSw.js';
3
+ import { I as ISkill, S as SkillSelectionConfig } from './skills-DWCC0K1B.js';
4
4
  import { I as IRAGAdapter } from './rag-La_Bo-J8.js';
5
5
 
6
6
  /**
@@ -242,6 +242,55 @@ interface GoalVerifierResult {
242
242
  /** Short human-readable reason for the verdict — surfaced in trace events */
243
243
  reason?: string;
244
244
  }
245
+ /**
246
+ * Lightweight description of a tool category, passed to a router so it can
247
+ * decide which categories are relevant to the current user message.
248
+ *
249
+ * @since 1.6.0
250
+ */
251
+ interface ToolCategoryInfo {
252
+ /** The category name (matches `IToolDefinition.category`). */
253
+ name: string;
254
+ /** Names of the tools belonging to this category — gives the router context. */
255
+ tools: string[];
256
+ }
257
+ /**
258
+ * Verdict returned by a router for a single user turn.
259
+ *
260
+ * @since 1.6.0
261
+ */
262
+ interface RouterDecision {
263
+ /**
264
+ * `'chat'` marks a purely conversational turn — the consumer/loop should
265
+ * skip the heavy pipeline (no tool exposure beyond always-available
266
+ * categories, and goal planning/verification suppressed for this turn).
267
+ * `'task'` runs the full pipeline.
268
+ */
269
+ mode: 'chat' | 'task';
270
+ /**
271
+ * Tool categories selected as relevant. Empty implies no categorized tools
272
+ * are exposed (typical for `mode: 'chat'`). Always-available categories
273
+ * (see `SessionConfig.alwaysAvailableCategories`) and uncategorized tools
274
+ * are exposed regardless of this list.
275
+ */
276
+ categories: string[];
277
+ /** Short human-readable reason — surfaced in trace events. */
278
+ reason?: string;
279
+ }
280
+ /**
281
+ * Pluggable router. Maps a user message to a {@link RouterDecision} that
282
+ * narrows the tool surface for the turn. Supply your own, or enable the
283
+ * built-in LLM router via `SessionConfig.enableRouting`.
284
+ *
285
+ * Implementations should **fail safe**: on any internal error, return
286
+ * `{ mode: 'task', categories: <all categories> }` so the agent never loses
287
+ * access to tools because routing hiccupped.
288
+ *
289
+ * @since 1.6.0
290
+ */
291
+ interface IRouterAdapter {
292
+ route(userMessage: string, availableCategories: ToolCategoryInfo[]): Promise<RouterDecision> | RouterDecision;
293
+ }
245
294
  /** Configuration for a lemura Session */
246
295
  interface SessionConfig {
247
296
  /** The provider adapter to use */
@@ -274,6 +323,17 @@ interface SessionConfig {
274
323
  * @since 1.4.0
275
324
  */
276
325
  activeDynamicTags?: string[];
326
+ /**
327
+ * Configuration for model-driven (`progressive`) skill selection. When any
328
+ * skill in `skills` has `strategy: 'progressive'`, Lemura automatically appends
329
+ * a skill catalog to the system prompt, registers the built-in `load_skill`
330
+ * tool (auto-trusted by the firewall), and resets progressive skills according
331
+ * to `persistence`. This object tunes that behaviour; it is ignored when no
332
+ * progressive skills are present.
333
+ *
334
+ * @since 1.7.0
335
+ */
336
+ skillSelection?: SkillSelectionConfig;
277
337
  /** RAG adapter */
278
338
  ragAdapter?: IRAGAdapter;
279
339
  /** Context compression strategies */
@@ -403,11 +463,47 @@ interface SessionConfig {
403
463
  * ]
404
464
  */
405
465
  mcpServers?: MCPServerConfig[];
466
+ /**
467
+ * When true, a router runs once at the start of each turn (before the ReAct
468
+ * loop) to classify the message (`chat`/`task`) and select relevant tool
469
+ * categories. Tools whose `category` is not selected are hidden from the
470
+ * model for that turn — fewer, more relevant tools means less confusion and
471
+ * lower token cost. A `chat` verdict also suppresses goal planning and
472
+ * verification for that turn.
473
+ *
474
+ * If `router` is supplied it is used; otherwise lemura's built-in
475
+ * {@link LLMRouter} is used. Defaults to false (no routing — all tools
476
+ * always exposed, identical to pre-1.6.0 behavior).
477
+ *
478
+ * @since 1.6.0
479
+ */
480
+ enableRouting?: boolean;
481
+ /**
482
+ * Custom router implementation. Takes precedence over the built-in router
483
+ * when `enableRouting` is true.
484
+ *
485
+ * @since 1.6.0
486
+ */
487
+ router?: IRouterAdapter;
488
+ /**
489
+ * Model used by the built-in router. Defaults to `config.model`. Point this
490
+ * at a small/cheap model for fast, low-cost classification.
491
+ *
492
+ * @since 1.6.0
493
+ */
494
+ routerModel?: string;
495
+ /**
496
+ * Categories that are always exposed regardless of the router decision
497
+ * (e.g. a scratchpad category). Uncategorized tools are also always exposed.
498
+ *
499
+ * @since 1.6.0
500
+ */
501
+ alwaysAvailableCategories?: string[];
406
502
  }
407
503
  /** Rich trace event for observability */
408
504
  interface TraceEvent {
409
505
  sessionId?: string;
410
- type: 'planning' | 'budget' | 'tool_call' | 'tool_result' | 'thinking' | 'system' | 'compression' | 'error' | 'skill' | 'verification';
506
+ type: 'planning' | 'budget' | 'tool_call' | 'tool_result' | 'thinking' | 'system' | 'compression' | 'error' | 'skill' | 'verification' | 'routing';
411
507
  name: string;
412
508
  input?: any;
413
509
  output?: any;
@@ -417,4 +513,4 @@ interface TraceEvent {
417
513
  metadata?: Record<string, any>;
418
514
  }
419
515
 
420
- export { type Goal as G, type IToolResponseProcessor as I, type MCPServerConfig as M, type SessionConfig as S, type ToolResponseEvaluation as T, type MCPToolDefinition as a, GoalInjector as b, type GoalVerifierResult as c, type MCPJsonRpcRequest as d, type MCPJsonRpcResponse as e, type MCPTransportType as f, type MediaConfig as g, type ToolDecision as h, type ToolExecutionBudget as i, type ToolFirewallConfig as j, type ToolFirewallRule as k, type TraceEvent as l };
516
+ export { type Goal as G, type IToolResponseProcessor as I, type MCPServerConfig as M, type RouterDecision as R, type SessionConfig as S, type ToolResponseEvaluation as T, type IRouterAdapter as a, type ToolCategoryInfo as b, type MCPToolDefinition as c, GoalInjector as d, type GoalVerifierResult as e, type MCPJsonRpcRequest as f, type MCPJsonRpcResponse as g, type MCPTransportType as h, type MediaConfig as i, type ToolDecision as j, type ToolExecutionBudget as k, type ToolFirewallConfig as l, type ToolFirewallRule as m, type TraceEvent as n };
@@ -1,6 +1,6 @@
1
- import { I as IToolDefinition, a as IProviderAdapter, b as IContextStrategy, S as ShortTermMemoryRegistry, c as IScratchpadAdapter, T as Turn } from './adapters-DAzmrg4l.mjs';
1
+ import { I as IToolDefinition, a as IProviderAdapter, b as IContextStrategy, S as ShortTermMemoryRegistry, c as IScratchpadAdapter, T as Turn } from './adapters-BN6und82.mjs';
2
2
  import { I as ILogger } from './logger-DxvKliuk.mjs';
3
- import { I as ISkill } from './skills-Y6D7zSSw.mjs';
3
+ import { I as ISkill, S as SkillSelectionConfig } from './skills-DWCC0K1B.mjs';
4
4
  import { I as IRAGAdapter } from './rag-La_Bo-J8.mjs';
5
5
 
6
6
  /**
@@ -242,6 +242,55 @@ interface GoalVerifierResult {
242
242
  /** Short human-readable reason for the verdict — surfaced in trace events */
243
243
  reason?: string;
244
244
  }
245
+ /**
246
+ * Lightweight description of a tool category, passed to a router so it can
247
+ * decide which categories are relevant to the current user message.
248
+ *
249
+ * @since 1.6.0
250
+ */
251
+ interface ToolCategoryInfo {
252
+ /** The category name (matches `IToolDefinition.category`). */
253
+ name: string;
254
+ /** Names of the tools belonging to this category — gives the router context. */
255
+ tools: string[];
256
+ }
257
+ /**
258
+ * Verdict returned by a router for a single user turn.
259
+ *
260
+ * @since 1.6.0
261
+ */
262
+ interface RouterDecision {
263
+ /**
264
+ * `'chat'` marks a purely conversational turn — the consumer/loop should
265
+ * skip the heavy pipeline (no tool exposure beyond always-available
266
+ * categories, and goal planning/verification suppressed for this turn).
267
+ * `'task'` runs the full pipeline.
268
+ */
269
+ mode: 'chat' | 'task';
270
+ /**
271
+ * Tool categories selected as relevant. Empty implies no categorized tools
272
+ * are exposed (typical for `mode: 'chat'`). Always-available categories
273
+ * (see `SessionConfig.alwaysAvailableCategories`) and uncategorized tools
274
+ * are exposed regardless of this list.
275
+ */
276
+ categories: string[];
277
+ /** Short human-readable reason — surfaced in trace events. */
278
+ reason?: string;
279
+ }
280
+ /**
281
+ * Pluggable router. Maps a user message to a {@link RouterDecision} that
282
+ * narrows the tool surface for the turn. Supply your own, or enable the
283
+ * built-in LLM router via `SessionConfig.enableRouting`.
284
+ *
285
+ * Implementations should **fail safe**: on any internal error, return
286
+ * `{ mode: 'task', categories: <all categories> }` so the agent never loses
287
+ * access to tools because routing hiccupped.
288
+ *
289
+ * @since 1.6.0
290
+ */
291
+ interface IRouterAdapter {
292
+ route(userMessage: string, availableCategories: ToolCategoryInfo[]): Promise<RouterDecision> | RouterDecision;
293
+ }
245
294
  /** Configuration for a lemura Session */
246
295
  interface SessionConfig {
247
296
  /** The provider adapter to use */
@@ -274,6 +323,17 @@ interface SessionConfig {
274
323
  * @since 1.4.0
275
324
  */
276
325
  activeDynamicTags?: string[];
326
+ /**
327
+ * Configuration for model-driven (`progressive`) skill selection. When any
328
+ * skill in `skills` has `strategy: 'progressive'`, Lemura automatically appends
329
+ * a skill catalog to the system prompt, registers the built-in `load_skill`
330
+ * tool (auto-trusted by the firewall), and resets progressive skills according
331
+ * to `persistence`. This object tunes that behaviour; it is ignored when no
332
+ * progressive skills are present.
333
+ *
334
+ * @since 1.7.0
335
+ */
336
+ skillSelection?: SkillSelectionConfig;
277
337
  /** RAG adapter */
278
338
  ragAdapter?: IRAGAdapter;
279
339
  /** Context compression strategies */
@@ -403,11 +463,47 @@ interface SessionConfig {
403
463
  * ]
404
464
  */
405
465
  mcpServers?: MCPServerConfig[];
466
+ /**
467
+ * When true, a router runs once at the start of each turn (before the ReAct
468
+ * loop) to classify the message (`chat`/`task`) and select relevant tool
469
+ * categories. Tools whose `category` is not selected are hidden from the
470
+ * model for that turn — fewer, more relevant tools means less confusion and
471
+ * lower token cost. A `chat` verdict also suppresses goal planning and
472
+ * verification for that turn.
473
+ *
474
+ * If `router` is supplied it is used; otherwise lemura's built-in
475
+ * {@link LLMRouter} is used. Defaults to false (no routing — all tools
476
+ * always exposed, identical to pre-1.6.0 behavior).
477
+ *
478
+ * @since 1.6.0
479
+ */
480
+ enableRouting?: boolean;
481
+ /**
482
+ * Custom router implementation. Takes precedence over the built-in router
483
+ * when `enableRouting` is true.
484
+ *
485
+ * @since 1.6.0
486
+ */
487
+ router?: IRouterAdapter;
488
+ /**
489
+ * Model used by the built-in router. Defaults to `config.model`. Point this
490
+ * at a small/cheap model for fast, low-cost classification.
491
+ *
492
+ * @since 1.6.0
493
+ */
494
+ routerModel?: string;
495
+ /**
496
+ * Categories that are always exposed regardless of the router decision
497
+ * (e.g. a scratchpad category). Uncategorized tools are also always exposed.
498
+ *
499
+ * @since 1.6.0
500
+ */
501
+ alwaysAvailableCategories?: string[];
406
502
  }
407
503
  /** Rich trace event for observability */
408
504
  interface TraceEvent {
409
505
  sessionId?: string;
410
- type: 'planning' | 'budget' | 'tool_call' | 'tool_result' | 'thinking' | 'system' | 'compression' | 'error' | 'skill' | 'verification';
506
+ type: 'planning' | 'budget' | 'tool_call' | 'tool_result' | 'thinking' | 'system' | 'compression' | 'error' | 'skill' | 'verification' | 'routing';
411
507
  name: string;
412
508
  input?: any;
413
509
  output?: any;
@@ -417,4 +513,4 @@ interface TraceEvent {
417
513
  metadata?: Record<string, any>;
418
514
  }
419
515
 
420
- export { type Goal as G, type IToolResponseProcessor as I, type MCPServerConfig as M, type SessionConfig as S, type ToolResponseEvaluation as T, type MCPToolDefinition as a, GoalInjector as b, type GoalVerifierResult as c, type MCPJsonRpcRequest as d, type MCPJsonRpcResponse as e, type MCPTransportType as f, type MediaConfig as g, type ToolDecision as h, type ToolExecutionBudget as i, type ToolFirewallConfig as j, type ToolFirewallRule as k, type TraceEvent as l };
516
+ export { type Goal as G, type IToolResponseProcessor as I, type MCPServerConfig as M, type RouterDecision as R, type SessionConfig as S, type ToolResponseEvaluation as T, type IRouterAdapter as a, type ToolCategoryInfo as b, type MCPToolDefinition as c, GoalInjector as d, type GoalVerifierResult as e, type MCPJsonRpcRequest as f, type MCPJsonRpcResponse as g, type MCPTransportType as h, type MediaConfig as i, type ToolDecision as j, type ToolExecutionBudget as k, type ToolFirewallConfig as l, type ToolFirewallRule as m, type TraceEvent as n };
@@ -1,5 +1,5 @@
1
- import { b as IContextStrategy, C as ContextWindow, a as IProviderAdapter, n as IStorageAdapter, c as IScratchpadAdapter } from '../adapters-DAzmrg4l.mjs';
2
- export { p as STMRegistryConfig, S as ShortTermMemoryRegistry } from '../adapters-DAzmrg4l.mjs';
1
+ import { b as IContextStrategy, C as ContextWindow, a as IProviderAdapter, n as IStorageAdapter, c as IScratchpadAdapter } from '../adapters-BN6und82.mjs';
2
+ export { p as STMRegistryConfig, S as ShortTermMemoryRegistry } from '../adapters-BN6und82.mjs';
3
3
  import '../rag-La_Bo-J8.mjs';
4
4
  import '../logger-DxvKliuk.mjs';
5
5
 
@@ -1,5 +1,5 @@
1
- import { b as IContextStrategy, C as ContextWindow, a as IProviderAdapter, n as IStorageAdapter, c as IScratchpadAdapter } from '../adapters-CIRkrCHl.js';
2
- export { p as STMRegistryConfig, S as ShortTermMemoryRegistry } from '../adapters-CIRkrCHl.js';
1
+ import { b as IContextStrategy, C as ContextWindow, a as IProviderAdapter, n as IStorageAdapter, c as IScratchpadAdapter } from '../adapters-CnsgefYR.js';
2
+ export { p as STMRegistryConfig, S as ShortTermMemoryRegistry } from '../adapters-CnsgefYR.js';
3
3
  import '../rag-La_Bo-J8.js';
4
4
  import '../logger-DxvKliuk.js';
5
5
 
package/dist/index.d.mts CHANGED
@@ -1,12 +1,12 @@
1
- import { a as IProviderAdapter, d as TranscriptionRequest, e as TranscriptionResponse, f as SynthesisRequest, A as AudioChunk, V as VisionRequest, g as VisionResponse, h as ImageGenRequest, i as ImageGenResponse, M as ModelInfo, C as ContextWindow, T as Turn, j as ContentBlock, I as IToolDefinition } from './adapters-DAzmrg4l.mjs';
2
- export { k as CompletionChunk, l as CompletionRequest, m as CompletionResponse, b as IContextStrategy, c as IScratchpadAdapter, n as IStorageAdapter, N as NormalizedMessage, o as STMItem, p as STMRegistryConfig, S as ShortTermMemoryRegistry, q as TokenUsage, r as ToolCall, s as ToolContext, t as ToolResult } from './adapters-DAzmrg4l.mjs';
3
- import { S as SessionConfig, G as Goal, I as IToolResponseProcessor, T as ToolResponseEvaluation, M as MCPServerConfig, a as MCPToolDefinition } from './agent-UBaqufhp.mjs';
4
- export { b as GoalInjector, c as GoalVerifierResult, d as MCPJsonRpcRequest, e as MCPJsonRpcResponse, f as MCPTransportType, g as MediaConfig, h as ToolDecision, i as ToolExecutionBudget, j as ToolFirewallConfig, k as ToolFirewallRule, l as TraceEvent } from './agent-UBaqufhp.mjs';
1
+ import { a as IProviderAdapter, d as TranscriptionRequest, e as TranscriptionResponse, f as SynthesisRequest, A as AudioChunk, V as VisionRequest, g as VisionResponse, h as ImageGenRequest, i as ImageGenResponse, M as ModelInfo, C as ContextWindow, T as Turn, j as ContentBlock, I as IToolDefinition } from './adapters-BN6und82.mjs';
2
+ export { k as CompletionChunk, l as CompletionRequest, m as CompletionResponse, b as IContextStrategy, c as IScratchpadAdapter, n as IStorageAdapter, N as NormalizedMessage, o as STMItem, p as STMRegistryConfig, S as ShortTermMemoryRegistry, q as TokenUsage, r as ToolCall, s as ToolContext, t as ToolResult } from './adapters-BN6und82.mjs';
3
+ import { S as SessionConfig, G as Goal, I as IToolResponseProcessor, T as ToolResponseEvaluation, a as IRouterAdapter, b as ToolCategoryInfo, R as RouterDecision, M as MCPServerConfig, c as MCPToolDefinition } from './agent-Dus44yQd.mjs';
4
+ export { d as GoalInjector, e as GoalVerifierResult, f as MCPJsonRpcRequest, g as MCPJsonRpcResponse, h as MCPTransportType, i as MediaConfig, j as ToolDecision, k as ToolExecutionBudget, l as ToolFirewallConfig, m as ToolFirewallRule, n as TraceEvent } from './agent-Dus44yQd.mjs';
5
5
  export { LemuraAdapterError, LemuraContextOverflowError, LemuraError, LemuraMCPConnectionError, LemuraMCPError, LemuraMCPTimeoutError, LemuraMaxIterationsError, LemuraSkillInjectionError, LemuraToolNotFoundError, LemuraToolTimeoutError, LemuraToolValidationError } from './types/index.mjs';
6
6
  import { I as ILogger } from './logger-DxvKliuk.mjs';
7
7
  export { L as LogLevel, a as LogMetadata, S as Severity } from './logger-DxvKliuk.mjs';
8
8
  export { I as IRAGAdapter, R as RAGDocument, a as RAGIngestOptions, b as RAGIngestRequest, c as RAGIngestResponse, d as RAGQueryRequest, e as RAGQueryResponse, f as RAGResult } from './rag-La_Bo-J8.mjs';
9
- export { I as ISkill, S as SkillStrategy } from './skills-Y6D7zSSw.mjs';
9
+ export { I as ISkill, S as SkillSelectionConfig, a as SkillStrategy } from './skills-DWCC0K1B.mjs';
10
10
  export { OpenAICompatibleAdapter, OpenAICompatibleAdapterConfig, RetryConfig } from './adapters/index.mjs';
11
11
  export { ContextManager, HistoryCompressionConfig, HistoryCompressionStrategy, InMemoryScratchpadAdapter, InMemoryStorageAdapter, SandwichCompressionConfig, SandwichCompressionStrategy, ScratchpadStrategy, SummaryInjectionConfig, SummaryInjectionStrategy } from './context/index.mjs';
12
12
  import { ToolRegistry } from './tools/index.mjs';
@@ -213,6 +213,8 @@ declare class SessionManager {
213
213
  private contextManager;
214
214
  private toolRegistry;
215
215
  private skillInjector;
216
+ /** True when any registered skill uses `strategy: 'progressive'`. */
217
+ private hasProgressiveSkills;
216
218
  private context;
217
219
  private adapter;
218
220
  private config;
@@ -226,6 +228,14 @@ declare class SessionManager {
226
228
  private toolResponseProcessor;
227
229
  private goalInjector;
228
230
  private continuationPlanner;
231
+ private router;
232
+ /**
233
+ * Tool categories selected by the router for the current turn, or `null` when
234
+ * routing is disabled / not yet run. When non-null, only tools whose
235
+ * `category` is in this set (plus always-available + uncategorized tools) are
236
+ * exposed to the model.
237
+ */
238
+ private routedCategories;
229
239
  /** Frozen goal/plan injection text keyed by turn index — used when staticSystemPrompt is on */
230
240
  private _turnInjections;
231
241
  private mcpRegistry;
@@ -358,6 +368,22 @@ declare class SessionManager {
358
368
  * `stream()` await `this.mcpReady` before executing.
359
369
  */
360
370
  private _initMCP;
371
+ /** Groups registered tools into {@link ToolCategoryInfo} by their `category`. */
372
+ private buildToolCategories;
373
+ /**
374
+ * Runs the router for the current turn (when enabled) and stores the selected
375
+ * categories in `this.routedCategories`. Returns the decision so the loop can
376
+ * suppress goal planning/verification on a `chat` verdict. Fail-safe: on a
377
+ * null/failed decision, routing is disabled for the turn (all tools exposed).
378
+ */
379
+ private _runRoutingStep;
380
+ /**
381
+ * Returns the tools to expose this turn, filtered by the router decision.
382
+ * Always exposes uncategorized tools and tools in always-available /
383
+ * routed-in categories. When routing is off (`routedCategories === null`),
384
+ * returns every tool — identical to pre-routing behavior.
385
+ */
386
+ private getActiveTools;
361
387
  /**
362
388
  * Runs a dedicated planning prompt against the LLM to decompose the user's
363
389
  * message into sub-goals and success criteria. Called once at the start of
@@ -404,6 +430,13 @@ declare class SessionManager {
404
430
  * @throws {LemuraMaxIterationsError} When the loop exceeds `maxIterations`
405
431
  */
406
432
  run(userMessage: string | ContentBlock[]): Promise<string>;
433
+ /**
434
+ * Resets progressive skills at the start of a turn when
435
+ * `skillSelection.persistence` is `'per_turn'` (the default), so each user
436
+ * message re-decides which skills to load from the catalog. No-op when
437
+ * persistence is `'session'` or there are no progressive skills.
438
+ */
439
+ private _resetProgressiveSkillsForTurn;
407
440
  /**
408
441
  * Runs the ReAct loop and streams the final assistant response token-by-token.
409
442
  *
@@ -529,6 +562,42 @@ declare class FinalResponseFormatter {
529
562
  static validateStructure(response: string): boolean;
530
563
  }
531
564
 
565
+ /**
566
+ * Options for the built-in {@link LLMRouter}.
567
+ *
568
+ * @since 1.6.0
569
+ */
570
+ interface LLMRouterOptions {
571
+ adapter: IProviderAdapter;
572
+ model: string;
573
+ logger: ILogger;
574
+ }
575
+ /**
576
+ * Built-in MetaRouter. Classifies a user message (`chat` vs `task`) and selects
577
+ * the relevant tool categories with a single temperature-0 LLM call.
578
+ *
579
+ * Two cheap guards run before the LLM:
580
+ * - **Conversational fast-path**: short greeting/acknowledgement input is
581
+ * classified `chat` with no categories and no LLM call.
582
+ * - **No categories**: if no categorized tools exist, routing is moot — returns
583
+ * `task` with an empty category list (uncategorized tools are always exposed
584
+ * by the caller anyway).
585
+ *
586
+ * Fails safe: any error returns `{ mode: 'task', categories: <all> }` so the
587
+ * agent never loses tool access because routing hiccupped.
588
+ *
589
+ * @since 1.6.0
590
+ */
591
+ declare class LLMRouter implements IRouterAdapter {
592
+ private adapter;
593
+ private model;
594
+ private logger;
595
+ /** Matches purely conversational input — greetings, thanks, acknowledgements. */
596
+ private static readonly CHAT_FAST_PATH;
597
+ constructor(opts: LLMRouterOptions);
598
+ route(userMessage: string, availableCategories: ToolCategoryInfo[]): Promise<RouterDecision>;
599
+ }
600
+
532
601
  /**
533
602
  * Low-level MCP server client.
534
603
  *
@@ -657,4 +726,4 @@ declare class MCPClientRegistry {
657
726
  private _bridge;
658
727
  }
659
728
 
660
- export { AudioChunk, ContentBlock, ContextWindow, type ContinuationPlan, ContinuationPlanner, type ContinuationStep, FinalResponseFormatter, Goal, ILogger, IProviderAdapter, IToolDefinition, IToolResponseProcessor, ImageGenRequest, ImageGenResponse, MCPClient, MCPClientRegistry, MCPServerConfig, MCPToolDefinition, MediaBridge, ModelInfo, SessionConfig, SessionManager, SkillInjector, type StepCondition, StepCounter, type StepVerifier, type StepVerifierResult, SynthesisRequest, ToolRegistry, ToolResponseEvaluation, ToolResponseProcessor, type ToolResponseProcessorConfig, TranscriptionRequest, TranscriptionResponse, Turn, VisionRequest, VisionResponse };
729
+ export { AudioChunk, ContentBlock, ContextWindow, type ContinuationPlan, ContinuationPlanner, type ContinuationStep, FinalResponseFormatter, Goal, ILogger, IProviderAdapter, IRouterAdapter, IToolDefinition, IToolResponseProcessor, ImageGenRequest, ImageGenResponse, LLMRouter, type LLMRouterOptions, MCPClient, MCPClientRegistry, MCPServerConfig, MCPToolDefinition, MediaBridge, ModelInfo, RouterDecision, SessionConfig, SessionManager, SkillInjector, type StepCondition, StepCounter, type StepVerifier, type StepVerifierResult, SynthesisRequest, ToolCategoryInfo, ToolRegistry, ToolResponseEvaluation, ToolResponseProcessor, type ToolResponseProcessorConfig, TranscriptionRequest, TranscriptionResponse, Turn, VisionRequest, VisionResponse };
package/dist/index.d.ts CHANGED
@@ -1,12 +1,12 @@
1
- import { a as IProviderAdapter, d as TranscriptionRequest, e as TranscriptionResponse, f as SynthesisRequest, A as AudioChunk, V as VisionRequest, g as VisionResponse, h as ImageGenRequest, i as ImageGenResponse, M as ModelInfo, C as ContextWindow, T as Turn, j as ContentBlock, I as IToolDefinition } from './adapters-CIRkrCHl.js';
2
- export { k as CompletionChunk, l as CompletionRequest, m as CompletionResponse, b as IContextStrategy, c as IScratchpadAdapter, n as IStorageAdapter, N as NormalizedMessage, o as STMItem, p as STMRegistryConfig, S as ShortTermMemoryRegistry, q as TokenUsage, r as ToolCall, s as ToolContext, t as ToolResult } from './adapters-CIRkrCHl.js';
3
- import { S as SessionConfig, G as Goal, I as IToolResponseProcessor, T as ToolResponseEvaluation, M as MCPServerConfig, a as MCPToolDefinition } from './agent-CknicweT.js';
4
- export { b as GoalInjector, c as GoalVerifierResult, d as MCPJsonRpcRequest, e as MCPJsonRpcResponse, f as MCPTransportType, g as MediaConfig, h as ToolDecision, i as ToolExecutionBudget, j as ToolFirewallConfig, k as ToolFirewallRule, l as TraceEvent } from './agent-CknicweT.js';
1
+ import { a as IProviderAdapter, d as TranscriptionRequest, e as TranscriptionResponse, f as SynthesisRequest, A as AudioChunk, V as VisionRequest, g as VisionResponse, h as ImageGenRequest, i as ImageGenResponse, M as ModelInfo, C as ContextWindow, T as Turn, j as ContentBlock, I as IToolDefinition } from './adapters-CnsgefYR.js';
2
+ export { k as CompletionChunk, l as CompletionRequest, m as CompletionResponse, b as IContextStrategy, c as IScratchpadAdapter, n as IStorageAdapter, N as NormalizedMessage, o as STMItem, p as STMRegistryConfig, S as ShortTermMemoryRegistry, q as TokenUsage, r as ToolCall, s as ToolContext, t as ToolResult } from './adapters-CnsgefYR.js';
3
+ import { S as SessionConfig, G as Goal, I as IToolResponseProcessor, T as ToolResponseEvaluation, a as IRouterAdapter, b as ToolCategoryInfo, R as RouterDecision, M as MCPServerConfig, c as MCPToolDefinition } from './agent-BcAg3gCz.js';
4
+ export { d as GoalInjector, e as GoalVerifierResult, f as MCPJsonRpcRequest, g as MCPJsonRpcResponse, h as MCPTransportType, i as MediaConfig, j as ToolDecision, k as ToolExecutionBudget, l as ToolFirewallConfig, m as ToolFirewallRule, n as TraceEvent } from './agent-BcAg3gCz.js';
5
5
  export { LemuraAdapterError, LemuraContextOverflowError, LemuraError, LemuraMCPConnectionError, LemuraMCPError, LemuraMCPTimeoutError, LemuraMaxIterationsError, LemuraSkillInjectionError, LemuraToolNotFoundError, LemuraToolTimeoutError, LemuraToolValidationError } from './types/index.js';
6
6
  import { I as ILogger } from './logger-DxvKliuk.js';
7
7
  export { L as LogLevel, a as LogMetadata, S as Severity } from './logger-DxvKliuk.js';
8
8
  export { I as IRAGAdapter, R as RAGDocument, a as RAGIngestOptions, b as RAGIngestRequest, c as RAGIngestResponse, d as RAGQueryRequest, e as RAGQueryResponse, f as RAGResult } from './rag-La_Bo-J8.js';
9
- export { I as ISkill, S as SkillStrategy } from './skills-Y6D7zSSw.js';
9
+ export { I as ISkill, S as SkillSelectionConfig, a as SkillStrategy } from './skills-DWCC0K1B.js';
10
10
  export { OpenAICompatibleAdapter, OpenAICompatibleAdapterConfig, RetryConfig } from './adapters/index.js';
11
11
  export { ContextManager, HistoryCompressionConfig, HistoryCompressionStrategy, InMemoryScratchpadAdapter, InMemoryStorageAdapter, SandwichCompressionConfig, SandwichCompressionStrategy, ScratchpadStrategy, SummaryInjectionConfig, SummaryInjectionStrategy } from './context/index.js';
12
12
  import { ToolRegistry } from './tools/index.js';
@@ -213,6 +213,8 @@ declare class SessionManager {
213
213
  private contextManager;
214
214
  private toolRegistry;
215
215
  private skillInjector;
216
+ /** True when any registered skill uses `strategy: 'progressive'`. */
217
+ private hasProgressiveSkills;
216
218
  private context;
217
219
  private adapter;
218
220
  private config;
@@ -226,6 +228,14 @@ declare class SessionManager {
226
228
  private toolResponseProcessor;
227
229
  private goalInjector;
228
230
  private continuationPlanner;
231
+ private router;
232
+ /**
233
+ * Tool categories selected by the router for the current turn, or `null` when
234
+ * routing is disabled / not yet run. When non-null, only tools whose
235
+ * `category` is in this set (plus always-available + uncategorized tools) are
236
+ * exposed to the model.
237
+ */
238
+ private routedCategories;
229
239
  /** Frozen goal/plan injection text keyed by turn index — used when staticSystemPrompt is on */
230
240
  private _turnInjections;
231
241
  private mcpRegistry;
@@ -358,6 +368,22 @@ declare class SessionManager {
358
368
  * `stream()` await `this.mcpReady` before executing.
359
369
  */
360
370
  private _initMCP;
371
+ /** Groups registered tools into {@link ToolCategoryInfo} by their `category`. */
372
+ private buildToolCategories;
373
+ /**
374
+ * Runs the router for the current turn (when enabled) and stores the selected
375
+ * categories in `this.routedCategories`. Returns the decision so the loop can
376
+ * suppress goal planning/verification on a `chat` verdict. Fail-safe: on a
377
+ * null/failed decision, routing is disabled for the turn (all tools exposed).
378
+ */
379
+ private _runRoutingStep;
380
+ /**
381
+ * Returns the tools to expose this turn, filtered by the router decision.
382
+ * Always exposes uncategorized tools and tools in always-available /
383
+ * routed-in categories. When routing is off (`routedCategories === null`),
384
+ * returns every tool — identical to pre-routing behavior.
385
+ */
386
+ private getActiveTools;
361
387
  /**
362
388
  * Runs a dedicated planning prompt against the LLM to decompose the user's
363
389
  * message into sub-goals and success criteria. Called once at the start of
@@ -404,6 +430,13 @@ declare class SessionManager {
404
430
  * @throws {LemuraMaxIterationsError} When the loop exceeds `maxIterations`
405
431
  */
406
432
  run(userMessage: string | ContentBlock[]): Promise<string>;
433
+ /**
434
+ * Resets progressive skills at the start of a turn when
435
+ * `skillSelection.persistence` is `'per_turn'` (the default), so each user
436
+ * message re-decides which skills to load from the catalog. No-op when
437
+ * persistence is `'session'` or there are no progressive skills.
438
+ */
439
+ private _resetProgressiveSkillsForTurn;
407
440
  /**
408
441
  * Runs the ReAct loop and streams the final assistant response token-by-token.
409
442
  *
@@ -529,6 +562,42 @@ declare class FinalResponseFormatter {
529
562
  static validateStructure(response: string): boolean;
530
563
  }
531
564
 
565
+ /**
566
+ * Options for the built-in {@link LLMRouter}.
567
+ *
568
+ * @since 1.6.0
569
+ */
570
+ interface LLMRouterOptions {
571
+ adapter: IProviderAdapter;
572
+ model: string;
573
+ logger: ILogger;
574
+ }
575
+ /**
576
+ * Built-in MetaRouter. Classifies a user message (`chat` vs `task`) and selects
577
+ * the relevant tool categories with a single temperature-0 LLM call.
578
+ *
579
+ * Two cheap guards run before the LLM:
580
+ * - **Conversational fast-path**: short greeting/acknowledgement input is
581
+ * classified `chat` with no categories and no LLM call.
582
+ * - **No categories**: if no categorized tools exist, routing is moot — returns
583
+ * `task` with an empty category list (uncategorized tools are always exposed
584
+ * by the caller anyway).
585
+ *
586
+ * Fails safe: any error returns `{ mode: 'task', categories: <all> }` so the
587
+ * agent never loses tool access because routing hiccupped.
588
+ *
589
+ * @since 1.6.0
590
+ */
591
+ declare class LLMRouter implements IRouterAdapter {
592
+ private adapter;
593
+ private model;
594
+ private logger;
595
+ /** Matches purely conversational input — greetings, thanks, acknowledgements. */
596
+ private static readonly CHAT_FAST_PATH;
597
+ constructor(opts: LLMRouterOptions);
598
+ route(userMessage: string, availableCategories: ToolCategoryInfo[]): Promise<RouterDecision>;
599
+ }
600
+
532
601
  /**
533
602
  * Low-level MCP server client.
534
603
  *
@@ -657,4 +726,4 @@ declare class MCPClientRegistry {
657
726
  private _bridge;
658
727
  }
659
728
 
660
- export { AudioChunk, ContentBlock, ContextWindow, type ContinuationPlan, ContinuationPlanner, type ContinuationStep, FinalResponseFormatter, Goal, ILogger, IProviderAdapter, IToolDefinition, IToolResponseProcessor, ImageGenRequest, ImageGenResponse, MCPClient, MCPClientRegistry, MCPServerConfig, MCPToolDefinition, MediaBridge, ModelInfo, SessionConfig, SessionManager, SkillInjector, type StepCondition, StepCounter, type StepVerifier, type StepVerifierResult, SynthesisRequest, ToolRegistry, ToolResponseEvaluation, ToolResponseProcessor, type ToolResponseProcessorConfig, TranscriptionRequest, TranscriptionResponse, Turn, VisionRequest, VisionResponse };
729
+ export { AudioChunk, ContentBlock, ContextWindow, type ContinuationPlan, ContinuationPlanner, type ContinuationStep, FinalResponseFormatter, Goal, ILogger, IProviderAdapter, IRouterAdapter, IToolDefinition, IToolResponseProcessor, ImageGenRequest, ImageGenResponse, LLMRouter, type LLMRouterOptions, MCPClient, MCPClientRegistry, MCPServerConfig, MCPToolDefinition, MediaBridge, ModelInfo, RouterDecision, SessionConfig, SessionManager, SkillInjector, type StepCondition, StepCounter, type StepVerifier, type StepVerifierResult, SynthesisRequest, ToolCategoryInfo, ToolRegistry, ToolResponseEvaluation, ToolResponseProcessor, type ToolResponseProcessorConfig, TranscriptionRequest, TranscriptionResponse, Turn, VisionRequest, VisionResponse };