lemura 1.5.3 → 1.5.5
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 +17 -0
- package/README.md +28 -9
- package/dist/{agent-Cd704J0J.d.ts → agent-CknicweT.d.ts} +18 -0
- package/dist/{agent-DFh31XIA.d.mts → agent-UBaqufhp.d.mts} +18 -0
- package/dist/index.d.mts +11 -2
- package/dist/index.d.ts +11 -2
- package/dist/index.js +121 -87
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +121 -87
- package/dist/index.mjs.map +1 -1
- package/dist/tools/index.d.mts +1 -1
- package/dist/tools/index.d.ts +1 -1
- package/dist/types/index.d.mts +1 -1
- package/dist/types/index.d.ts +1 -1
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -5,6 +5,23 @@ 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.5.5] - 2026-06-01
|
|
9
|
+
|
|
10
|
+
### Fixed
|
|
11
|
+
|
|
12
|
+
- **Duplicated response in `stream()` during goal correction** (significant): `stream()` yielded the model's first answer to the caller *live*, then ran goal verification *afterwards*. When the verifier returned `achieved: false` and a correction re-entry followed, the corrected answer was streamed immediately after the already-emitted first answer — surfacing both as one duplicated response (contradicting the documented "delivers only the clean final response" contract). The final response is now **buffered** until verification settles; a rejected attempt is silently discarded and corrected, so the stream emits only the single approved answer. `run()` was unaffected (it already buffered).
|
|
13
|
+
|
|
14
|
+
## [1.5.4] - 2026-05-30
|
|
15
|
+
|
|
16
|
+
### Fixed
|
|
17
|
+
|
|
18
|
+
- **Goal verification could not recover an incomplete response** (significant): when the goal verifier returned `achieved: false`, the runtime ran a single **tool-less** `complete()` correction whose output was never re-verified (the run was flagged done after the first check) and, in `stream()`, was never sent to the caller. A response missing a step that required a tool (read a file, write output, …) could therefore never actually be completed. The verifier now re-enters the ReAct loop with a corrective user turn so the model has **full tool access** and the corrected answer is **streamed**, bounded by the new `maxGoalCorrections` budget (default `1`). When the budget is exhausted and the goal is still unmet, a visible Goal Verification Warning is appended/streamed as before. Applies to both `run()` and `stream()`.
|
|
19
|
+
|
|
20
|
+
### Added
|
|
21
|
+
|
|
22
|
+
- **`maxGoalCorrections`** (`SessionConfig`, default `1`): maximum goal-verification corrective re-entries per run. Set to `0` to disable corrective re-entry (a warning is surfaced instead).
|
|
23
|
+
- **`goalProgressReconciliation`** (`SessionConfig`, default `false`): when enabled, the agent periodically (every `goalInjectionN` tool rounds) reconciles which decomposed sub-goals are already complete and marks them done, so the re-injected goal block reflects real progress instead of always showing every sub-goal as pending. Counters goal drift on long runs at the cost of one small LLM call per reconciliation. This wires up `GoalInjector.markSubGoalDone()`, which was previously never invoked.
|
|
24
|
+
|
|
8
25
|
## [1.5.3] - 2026-05-30
|
|
9
26
|
|
|
10
27
|
### Fixed
|
package/README.md
CHANGED
|
@@ -1,4 +1,6 @@
|
|
|
1
|
+
<p align="center">
|
|
1
2
|
<img src="https://raw.githubusercontent.com/rzafiamy/lemura/main/sites/docs/public/lemura-logo.png" alt="lemura logo" width="200" />
|
|
3
|
+
</p>
|
|
2
4
|
|
|
3
5
|
# lemura
|
|
4
6
|
|
|
@@ -24,10 +26,11 @@
|
|
|
24
26
|
|
|
25
27
|
- **🧠 Dynamic Skill Market**: Switch skills on/off at runtime via tags, names, or tool dependencies.
|
|
26
28
|
- **🔌 Native MCP Support**: Connect to any Model Context Protocol server with custom header support (Auth).
|
|
27
|
-
- **🛡️ Tool Firewall**: Fully integrated ask/accept/deny policy layer for secure tool execution.
|
|
28
|
-
- **🎯 Goal Maintenance**: LLM-powered sub-goal decomposition and
|
|
29
|
-
- **🧹
|
|
30
|
-
- **🌊 Native Streaming**:
|
|
29
|
+
- **🛡️ Tool Firewall**: Fully integrated ask/accept/deny policy layer for secure tool execution — fail-safe by design.
|
|
30
|
+
- **🎯 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.
|
|
31
|
+
- **🧹 Context Compression**: Sandwich, history, and summary-injection strategies keep long conversations within budget while ensuring the model never "forgets" the context.
|
|
32
|
+
- **🌊 Native Streaming**: `run()` and `stream()` share one ReAct core — `stream()` completes all tool use and verification, then emits the final answer token-by-token.
|
|
33
|
+
- **📚 RAG Connector**: Pluggable `IRAGAdapter` interface plus a bundled in-memory adapter for ingest → query → context injection.
|
|
31
34
|
- **📊 Observability**: Detailed tracing, token tracking, and structured logging with actionable hints.
|
|
32
35
|
|
|
33
36
|
## 🚀 Install
|
|
@@ -142,13 +145,29 @@ Explore the architecture and advanced capabilities of `lemura` at [lemura.makix.
|
|
|
142
145
|
|
|
143
146
|
| Export | Description |
|
|
144
147
|
|---|---|
|
|
145
|
-
| `SessionManager` | The main entry point orchestrating the ReAct loop and
|
|
146
|
-
| `ContextManager` | Manages the conversation history using compression strategies. |
|
|
147
|
-
| `OpenAICompatibleAdapter` | Reference adapter for OpenAI, Groq, Together,
|
|
148
|
-
| `ToolRegistry` | Registers and executes tools
|
|
148
|
+
| `SessionManager` | The main entry point orchestrating the ReAct loop, tools, goals, and streaming. |
|
|
149
|
+
| `ContextManager` | Manages the conversation history using pluggable compression strategies. |
|
|
150
|
+
| `OpenAICompatibleAdapter` | Reference adapter for OpenAI, Groq, Together, Ollama, and any OpenAI-compatible endpoint. |
|
|
151
|
+
| `ToolRegistry` | Registers, validates, and executes tools with timeout and budget enforcement. |
|
|
149
152
|
| `SkillInjector` | Loads and formats YAML/Markdown skills into system prompts. |
|
|
153
|
+
| `MCPClient` / `MCPClientRegistry` | Connect to Model Context Protocol servers and register their tools. |
|
|
154
|
+
| `InMemoryRAGAdapter` | Self-contained RAG adapter for testing the ingest → query round-trip. |
|
|
150
155
|
| `DefaultLogger` | Colorized logger with Problem/Hints metadata support. |
|
|
151
156
|
|
|
157
|
+
### Subpath Exports
|
|
158
|
+
|
|
159
|
+
Each layer is independently importable so consumers only bundle what they use:
|
|
160
|
+
|
|
161
|
+
```ts
|
|
162
|
+
import { SandwichCompressionStrategy } from 'lemura/context';
|
|
163
|
+
import { OpenAICompatibleAdapter } from 'lemura/adapters';
|
|
164
|
+
import { ToolRegistry } from 'lemura/tools';
|
|
165
|
+
import { SkillInjector } from 'lemura/skills';
|
|
166
|
+
import { InMemoryRAGAdapter } from 'lemura/rag';
|
|
167
|
+
import { MCPClient } from 'lemura/mcp';
|
|
168
|
+
import { DefaultLogger } from 'lemura/logger';
|
|
169
|
+
```
|
|
170
|
+
|
|
152
171
|
## 🪵 Logging and Tracing
|
|
153
172
|
|
|
154
173
|
`lemura` features a premium, structured logging system designed for developer experience. It provides colorized output and actionable hints for errors.
|
|
@@ -190,7 +209,7 @@ When an error occurs (like an invalid API key), `lemura` provides beautiful, str
|
|
|
190
209
|
|
|
191
210
|
## 🤝 Contributing
|
|
192
211
|
|
|
193
|
-
We welcome contributions! Please read our [Internal Rules](./.
|
|
212
|
+
We welcome contributions! Please read our [Internal Rules](./.agent/rules/Project.md) and [Documentation Guidelines](./.agent/rules/Documentation.md) before submitting a PR.
|
|
194
213
|
|
|
195
214
|
## 📄 License
|
|
196
215
|
|
|
@@ -304,6 +304,24 @@ interface SessionConfig {
|
|
|
304
304
|
* @since 1.5.0
|
|
305
305
|
*/
|
|
306
306
|
enableGoalVerification?: boolean;
|
|
307
|
+
/**
|
|
308
|
+
* Maximum number of goal-verification corrections per run. When verification
|
|
309
|
+
* finds the response incomplete, the agent re-enters the ReAct loop (with full
|
|
310
|
+
* tool access) up to this many times to actually resolve what is missing,
|
|
311
|
+
* rather than emitting a tool-less one-shot rewrite. Defaults to 1.
|
|
312
|
+
* Set to 0 to disable corrective re-entry (a warning is surfaced instead).
|
|
313
|
+
* @since 1.5.4
|
|
314
|
+
*/
|
|
315
|
+
maxGoalCorrections?: number;
|
|
316
|
+
/**
|
|
317
|
+
* When true, the agent periodically reconciles which decomposed sub-goals are
|
|
318
|
+
* already complete (one small LLM call every `goalInjectionN` tool rounds) and
|
|
319
|
+
* marks them done, so the re-injected goal block reflects real progress instead
|
|
320
|
+
* of always showing every sub-goal as pending. Counters goal drift on long runs.
|
|
321
|
+
* Defaults to false (no extra calls). Requires `enableGoalPlanning`.
|
|
322
|
+
* @since 1.5.4
|
|
323
|
+
*/
|
|
324
|
+
goalProgressReconciliation?: boolean;
|
|
307
325
|
goalInjectionFrequency?: 'always' | 'every_N_turns' | 'on_compression';
|
|
308
326
|
goalInjectionPosition?: 'system_prompt' | 'pre_turn';
|
|
309
327
|
/** Skill budget — max tokens the skill injection block may consume */
|
|
@@ -304,6 +304,24 @@ interface SessionConfig {
|
|
|
304
304
|
* @since 1.5.0
|
|
305
305
|
*/
|
|
306
306
|
enableGoalVerification?: boolean;
|
|
307
|
+
/**
|
|
308
|
+
* Maximum number of goal-verification corrections per run. When verification
|
|
309
|
+
* finds the response incomplete, the agent re-enters the ReAct loop (with full
|
|
310
|
+
* tool access) up to this many times to actually resolve what is missing,
|
|
311
|
+
* rather than emitting a tool-less one-shot rewrite. Defaults to 1.
|
|
312
|
+
* Set to 0 to disable corrective re-entry (a warning is surfaced instead).
|
|
313
|
+
* @since 1.5.4
|
|
314
|
+
*/
|
|
315
|
+
maxGoalCorrections?: number;
|
|
316
|
+
/**
|
|
317
|
+
* When true, the agent periodically reconciles which decomposed sub-goals are
|
|
318
|
+
* already complete (one small LLM call every `goalInjectionN` tool rounds) and
|
|
319
|
+
* marks them done, so the re-injected goal block reflects real progress instead
|
|
320
|
+
* of always showing every sub-goal as pending. Counters goal drift on long runs.
|
|
321
|
+
* Defaults to false (no extra calls). Requires `enableGoalPlanning`.
|
|
322
|
+
* @since 1.5.4
|
|
323
|
+
*/
|
|
324
|
+
goalProgressReconciliation?: boolean;
|
|
307
325
|
goalInjectionFrequency?: 'always' | 'every_N_turns' | 'on_compression';
|
|
308
326
|
goalInjectionPosition?: 'system_prompt' | 'pre_turn';
|
|
309
327
|
/** Skill budget — max tokens the skill injection block may consume */
|
package/dist/index.d.mts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
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
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-
|
|
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-
|
|
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';
|
|
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';
|
|
@@ -365,6 +365,15 @@ declare class SessionManager {
|
|
|
365
365
|
* manually set via `setGoal()`.
|
|
366
366
|
*/
|
|
367
367
|
private _runMiniPlanningStep;
|
|
368
|
+
/**
|
|
369
|
+
* Reconciles sub-goal completion against the recent conversation so the
|
|
370
|
+
* re-injected goal block reflects real progress (anti-drift). Runs only when
|
|
371
|
+
* `goalProgressReconciliation` is enabled, and is a no-op when there are no
|
|
372
|
+
* pending sub-goals. One small, non-fatal LLM call; failures are swallowed.
|
|
373
|
+
*
|
|
374
|
+
* @since 1.5.4
|
|
375
|
+
*/
|
|
376
|
+
private _reconcileSubGoals;
|
|
368
377
|
/** Builds the system prompt, injecting skills and goal if configured. */
|
|
369
378
|
private buildSystemPrompt;
|
|
370
379
|
/** Builds the messages array for the provider from the current context. */
|
package/dist/index.d.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
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
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-
|
|
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-
|
|
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';
|
|
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';
|
|
@@ -365,6 +365,15 @@ declare class SessionManager {
|
|
|
365
365
|
* manually set via `setGoal()`.
|
|
366
366
|
*/
|
|
367
367
|
private _runMiniPlanningStep;
|
|
368
|
+
/**
|
|
369
|
+
* Reconciles sub-goal completion against the recent conversation so the
|
|
370
|
+
* re-injected goal block reflects real progress (anti-drift). Runs only when
|
|
371
|
+
* `goalProgressReconciliation` is enabled, and is a no-op when there are no
|
|
372
|
+
* pending sub-goals. One small, non-fatal LLM call; failures are swallowed.
|
|
373
|
+
*
|
|
374
|
+
* @since 1.5.4
|
|
375
|
+
*/
|
|
376
|
+
private _reconcileSubGoals;
|
|
368
377
|
/** Builds the system prompt, injecting skills and goal if configured. */
|
|
369
378
|
private buildSystemPrompt;
|
|
370
379
|
/** Builds the messages array for the provider from the current context. */
|
package/dist/index.js
CHANGED
|
@@ -3028,6 +3028,64 @@ Respond ONLY with valid JSON (no markdown, no explanations):
|
|
|
3028
3028
|
this.logger.warn(`[GoalInjector] Mini-planning step failed: ${err.message ?? String(err)}`);
|
|
3029
3029
|
}
|
|
3030
3030
|
}
|
|
3031
|
+
/**
|
|
3032
|
+
* Reconciles sub-goal completion against the recent conversation so the
|
|
3033
|
+
* re-injected goal block reflects real progress (anti-drift). Runs only when
|
|
3034
|
+
* `goalProgressReconciliation` is enabled, and is a no-op when there are no
|
|
3035
|
+
* pending sub-goals. One small, non-fatal LLM call; failures are swallowed.
|
|
3036
|
+
*
|
|
3037
|
+
* @since 1.5.4
|
|
3038
|
+
*/
|
|
3039
|
+
async _reconcileSubGoals() {
|
|
3040
|
+
if (!this.config.goalProgressReconciliation) return;
|
|
3041
|
+
if (!this.goalInjector) return;
|
|
3042
|
+
const goal = this.goalInjector.getGoal();
|
|
3043
|
+
const completed = new Set(goal.completedSubGoals ?? []);
|
|
3044
|
+
const pending = (goal.decomposition ?? []).filter((sg) => !completed.has(sg));
|
|
3045
|
+
if (pending.length === 0) return;
|
|
3046
|
+
const recentTurns = this.context.turns.slice(-6).map((t) => {
|
|
3047
|
+
const text = typeof t.content === "string" ? t.content : JSON.stringify(t.content);
|
|
3048
|
+
return `[${t.role}]: ${text.slice(0, 400)}`;
|
|
3049
|
+
}).join("\n\n");
|
|
3050
|
+
const pendingList = pending.map((sg, i) => `${i + 1}. ${sg}`).join("\n");
|
|
3051
|
+
try {
|
|
3052
|
+
const response = await this.adapter.complete({
|
|
3053
|
+
model: this.config.model,
|
|
3054
|
+
temperature: 0,
|
|
3055
|
+
maxTokens: 256,
|
|
3056
|
+
messages: [
|
|
3057
|
+
{
|
|
3058
|
+
role: "system",
|
|
3059
|
+
content: 'You track sub-goal progress. Given pending sub-goals and the recent conversation, return ONLY the 1-based indices of sub-goals that are now fully completed. Respond with valid JSON only, no prose: {"completed": number[]}'
|
|
3060
|
+
},
|
|
3061
|
+
{
|
|
3062
|
+
role: "user",
|
|
3063
|
+
content: `Pending sub-goals:
|
|
3064
|
+
${pendingList}
|
|
3065
|
+
|
|
3066
|
+
Recent conversation:
|
|
3067
|
+
${recentTurns}
|
|
3068
|
+
|
|
3069
|
+
Which pending sub-goals are now fully completed?`
|
|
3070
|
+
}
|
|
3071
|
+
]
|
|
3072
|
+
});
|
|
3073
|
+
const match = response.content.match(/\{[\s\S]*\}/);
|
|
3074
|
+
if (!match) return;
|
|
3075
|
+
const parsed = JSON.parse(match[0]);
|
|
3076
|
+
if (!Array.isArray(parsed.completed)) return;
|
|
3077
|
+
for (const idx of parsed.completed) {
|
|
3078
|
+
const sg = pending[idx - 1];
|
|
3079
|
+
if (sg) {
|
|
3080
|
+
this.goalInjector.markSubGoalDone(sg);
|
|
3081
|
+
this.emitTrace("planning", "subgoal_done", { subGoal: sg });
|
|
3082
|
+
}
|
|
3083
|
+
}
|
|
3084
|
+
this.context.metadata["goal"] = this.goalInjector.getGoal();
|
|
3085
|
+
} catch (err) {
|
|
3086
|
+
this.logger.warn(`[GoalInjector] Sub-goal reconciliation failed (non-fatal): ${err.message ?? String(err)}`);
|
|
3087
|
+
}
|
|
3088
|
+
}
|
|
3031
3089
|
// -----------------------------------------------------------------------
|
|
3032
3090
|
// Internal helpers
|
|
3033
3091
|
// -----------------------------------------------------------------------
|
|
@@ -3492,6 +3550,7 @@ ${blocks.join("\n\n")}
|
|
|
3492
3550
|
this.iterations = 0;
|
|
3493
3551
|
this.stepCounter = new StepCounter(this.config.maxSteps ?? 20);
|
|
3494
3552
|
const maxCompletionTokens = this.config.maxCompletionTokens ?? 4e3;
|
|
3553
|
+
let correctionsRemaining = this.config.maxGoalCorrections ?? 1;
|
|
3495
3554
|
while (this.iterations < maxIts) {
|
|
3496
3555
|
this.iterations++;
|
|
3497
3556
|
this.logger.debug(`[stream] ReAct Iteration ${this.iterations}/${maxIts}`);
|
|
@@ -3553,6 +3612,9 @@ ${blocks.join("\n\n")}
|
|
|
3553
3612
|
if (this.config.onTurn) this.config.onTurn(toolTurn);
|
|
3554
3613
|
}
|
|
3555
3614
|
if (this.goalInjector) this.goalInjector.incrementTurn();
|
|
3615
|
+
if (this.config.goalProgressReconciliation && this.iterations % (this.config.goalInjectionN ?? 3) === 0) {
|
|
3616
|
+
await this._reconcileSubGoals();
|
|
3617
|
+
}
|
|
3556
3618
|
continue;
|
|
3557
3619
|
}
|
|
3558
3620
|
this.context.tokenCount = this.context.turns.reduce((sum, t) => sum + t.tokenCount, 0) + this.adapter.estimateTokens(this.context.systemPrompt || "");
|
|
@@ -3571,7 +3633,6 @@ ${blocks.join("\n\n")}
|
|
|
3571
3633
|
if (chunk.delta) {
|
|
3572
3634
|
accumulated += chunk.delta;
|
|
3573
3635
|
finalTokenCount += Math.ceil(chunk.delta.length / 4);
|
|
3574
|
-
yield chunk.delta;
|
|
3575
3636
|
}
|
|
3576
3637
|
if (chunk.finished) {
|
|
3577
3638
|
finalFinishReason = chunk.finishReason;
|
|
@@ -3594,62 +3655,49 @@ ${blocks.join("\n\n")}
|
|
|
3594
3655
|
if (this.config.onTurn) this.config.onTurn(finalTurn);
|
|
3595
3656
|
if (finalFinishReason === "stop") {
|
|
3596
3657
|
const verdict = await this._verifyGoal(this.context.turns);
|
|
3597
|
-
if (verdict && !verdict.achieved && verdict.missing) {
|
|
3598
|
-
|
|
3658
|
+
if (verdict && !verdict.achieved && verdict.missing && correctionsRemaining > 0 && this.iterations < maxIts) {
|
|
3659
|
+
correctionsRemaining--;
|
|
3660
|
+
this.logger.info(`[GoalVerifier] Incomplete \u2014 re-entering loop with tools to correct: "${verdict.missing}" (${correctionsRemaining} correction(s) left)`);
|
|
3599
3661
|
this.emitTrace("verification", "goal_correction_start", {
|
|
3600
3662
|
missing: verdict.missing,
|
|
3601
|
-
reason: verdict.reason
|
|
3663
|
+
reason: verdict.reason,
|
|
3664
|
+
correctionsRemaining
|
|
3602
3665
|
});
|
|
3603
|
-
|
|
3604
|
-
|
|
3605
|
-
|
|
3606
|
-
|
|
3607
|
-
|
|
3608
|
-
|
|
3609
|
-
|
|
3610
|
-
|
|
3611
|
-
|
|
3612
|
-
|
|
3613
|
-
|
|
3614
|
-
|
|
3615
|
-
|
|
3616
|
-
|
|
3617
|
-
|
|
3618
|
-
|
|
3619
|
-
|
|
3620
|
-
|
|
3621
|
-
|
|
3622
|
-
|
|
3623
|
-
|
|
3624
|
-
} catch (err) {
|
|
3625
|
-
const errMsg = err.message;
|
|
3626
|
-
this.logger.warn(`[GoalVerifier] Correction failed (non-fatal): ${errMsg}`);
|
|
3627
|
-
this.emitTrace("error", "goal_correction_failed", { error: errMsg }, null, null, "error");
|
|
3628
|
-
}
|
|
3629
|
-
const finalVerdict = await this._verifyGoal(this.context.turns);
|
|
3630
|
-
if (finalVerdict && !finalVerdict.achieved) {
|
|
3631
|
-
this.logger.warn(`[GoalVerifier] Final verification failed: ${finalVerdict.reason}`);
|
|
3632
|
-
this.emitTrace("verification", "goal_verification_result", {
|
|
3633
|
-
achieved: false,
|
|
3634
|
-
reason: finalVerdict.reason,
|
|
3635
|
-
missing: finalVerdict.missing
|
|
3636
|
-
}, null, null, "error");
|
|
3637
|
-
const warningBlock = `
|
|
3666
|
+
const directive = `[Goal verification found the previous response incomplete. Continue working \u2014 use tools as needed \u2014 to address what is still missing, then provide the complete final answer.]
|
|
3667
|
+
|
|
3668
|
+
Still missing: ${verdict.missing}`;
|
|
3669
|
+
this.context.turns.push({
|
|
3670
|
+
role: "user",
|
|
3671
|
+
content: directive,
|
|
3672
|
+
tokenCount: this.adapter.estimateTokens(directive),
|
|
3673
|
+
turnIndex: this.context.turns.length,
|
|
3674
|
+
compressed: false
|
|
3675
|
+
});
|
|
3676
|
+
if (this.goalInjector) this.goalInjector.incrementTurn();
|
|
3677
|
+
continue;
|
|
3678
|
+
}
|
|
3679
|
+
if (verdict && !verdict.achieved) {
|
|
3680
|
+
this.logger.warn(`[GoalVerifier] Goal still unmet after corrections: ${verdict.reason}`);
|
|
3681
|
+
this.emitTrace("verification", "goal_verification_result", {
|
|
3682
|
+
achieved: false,
|
|
3683
|
+
reason: verdict.reason,
|
|
3684
|
+
missing: verdict.missing
|
|
3685
|
+
}, null, null, "error");
|
|
3686
|
+
const warningBlock = `
|
|
3638
3687
|
|
|
3639
3688
|
---
|
|
3640
3689
|
|
|
3641
3690
|
\u26A0\uFE0F **Goal Verification Warning**
|
|
3642
3691
|
* **Status:** Success criteria not fully met.
|
|
3643
|
-
* **Reason:** ${
|
|
3644
|
-
* **Missing:** ${
|
|
3692
|
+
* **Reason:** ${verdict.reason ?? "Unknown"}
|
|
3693
|
+
* **Missing:** ${verdict.missing ?? "Not specified"}
|
|
3645
3694
|
|
|
3646
3695
|
`;
|
|
3647
|
-
|
|
3648
|
-
|
|
3649
|
-
if (lastTurn) lastTurn.content = lastTurn.content + warningBlock;
|
|
3650
|
-
}
|
|
3696
|
+
accumulated += warningBlock;
|
|
3697
|
+
finalTurn.content = accumulated;
|
|
3651
3698
|
}
|
|
3652
3699
|
}
|
|
3700
|
+
if (accumulated) yield accumulated;
|
|
3653
3701
|
this.logger.info(`[stream] Streaming run completed`);
|
|
3654
3702
|
return;
|
|
3655
3703
|
}
|
|
@@ -3699,7 +3747,7 @@ ${blocks.join("\n\n")}
|
|
|
3699
3747
|
this.iterations = 0;
|
|
3700
3748
|
this.stepCounter = new StepCounter(this.config.maxSteps ?? 20);
|
|
3701
3749
|
const maxCompletionTokens = this.config.maxCompletionTokens ?? 4e3;
|
|
3702
|
-
let
|
|
3750
|
+
let correctionsRemaining = this.config.maxGoalCorrections ?? 1;
|
|
3703
3751
|
while (this.iterations < maxIts) {
|
|
3704
3752
|
this.iterations++;
|
|
3705
3753
|
this.logger.debug(`[${opts.label}] ReAct Iteration ${this.iterations}/${maxIts}`);
|
|
@@ -3834,6 +3882,9 @@ ${blocks.join("\n\n")}
|
|
|
3834
3882
|
if (this.config.onTurn) this.config.onTurn(toolTurn);
|
|
3835
3883
|
}
|
|
3836
3884
|
if (this.goalInjector) this.goalInjector.incrementTurn();
|
|
3885
|
+
if (this.config.goalProgressReconciliation && this.iterations % (this.config.goalInjectionN ?? 3) === 0) {
|
|
3886
|
+
await this._reconcileSubGoals();
|
|
3887
|
+
}
|
|
3837
3888
|
continue;
|
|
3838
3889
|
}
|
|
3839
3890
|
if (response.finishReason === "stop" || response.finishReason === "max_tokens" || response.finishReason === "error") {
|
|
@@ -3846,52 +3897,35 @@ ${blocks.join("\n\n")}
|
|
|
3846
3897
|
};
|
|
3847
3898
|
this.context.turns.push(finalTurn);
|
|
3848
3899
|
if (this.config.onTurn) this.config.onTurn(finalTurn);
|
|
3849
|
-
if (response.finishReason === "stop"
|
|
3850
|
-
goalVerificationDone = true;
|
|
3900
|
+
if (response.finishReason === "stop") {
|
|
3851
3901
|
const verdict = await this._verifyGoal(this.context.turns);
|
|
3852
|
-
if (verdict && !verdict.achieved && verdict.missing) {
|
|
3853
|
-
|
|
3902
|
+
if (verdict && !verdict.achieved && verdict.missing && correctionsRemaining > 0 && this.iterations < maxIts) {
|
|
3903
|
+
correctionsRemaining--;
|
|
3904
|
+
this.logger.info(`[GoalVerifier] Incomplete \u2014 re-entering loop with tools to correct: "${verdict.missing}" (${correctionsRemaining} correction(s) left)`);
|
|
3854
3905
|
this.emitTrace("verification", "goal_correction_start", {
|
|
3855
3906
|
missing: verdict.missing,
|
|
3856
|
-
reason: verdict.reason
|
|
3907
|
+
reason: verdict.reason,
|
|
3908
|
+
correctionsRemaining
|
|
3857
3909
|
});
|
|
3858
|
-
|
|
3859
|
-
|
|
3860
|
-
|
|
3861
|
-
|
|
3862
|
-
|
|
3863
|
-
|
|
3864
|
-
|
|
3865
|
-
|
|
3866
|
-
|
|
3867
|
-
|
|
3868
|
-
|
|
3869
|
-
|
|
3870
|
-
this.context.turns.push({
|
|
3871
|
-
role: "assistant",
|
|
3872
|
-
content: correction.content,
|
|
3873
|
-
tokenCount: correction.usage?.completionTokens ?? this.adapter.estimateTokens(correction.content),
|
|
3874
|
-
turnIndex: this.context.turns.length,
|
|
3875
|
-
compressed: false
|
|
3876
|
-
});
|
|
3877
|
-
this.emitTrace("verification", "goal_correction_done", {
|
|
3878
|
-
missing: verdict.missing,
|
|
3879
|
-
correctionTokens: correction.usage?.completionTokens
|
|
3880
|
-
});
|
|
3881
|
-
}
|
|
3882
|
-
} catch (err) {
|
|
3883
|
-
const errMsg = err.message;
|
|
3884
|
-
this.logger.warn(`[GoalVerifier] Correction failed (non-fatal): ${errMsg}`);
|
|
3885
|
-
this.emitTrace("error", "goal_correction_failed", { error: errMsg }, null, null, "error");
|
|
3886
|
-
}
|
|
3910
|
+
const directive = `[Goal verification found the previous response incomplete. Continue working \u2014 use tools as needed \u2014 to address what is still missing, then provide the complete final answer.]
|
|
3911
|
+
|
|
3912
|
+
Still missing: ${verdict.missing}`;
|
|
3913
|
+
this.context.turns.push({
|
|
3914
|
+
role: "user",
|
|
3915
|
+
content: directive,
|
|
3916
|
+
tokenCount: this.adapter.estimateTokens(directive),
|
|
3917
|
+
turnIndex: this.context.turns.length,
|
|
3918
|
+
compressed: false
|
|
3919
|
+
});
|
|
3920
|
+
if (this.goalInjector) this.goalInjector.incrementTurn();
|
|
3921
|
+
continue;
|
|
3887
3922
|
}
|
|
3888
|
-
|
|
3889
|
-
|
|
3890
|
-
this.logger.warn(`[GoalVerifier] Final verification failed: ${finalVerdict.reason}`);
|
|
3923
|
+
if (verdict && !verdict.achieved) {
|
|
3924
|
+
this.logger.warn(`[GoalVerifier] Goal still unmet after corrections: ${verdict.reason}`);
|
|
3891
3925
|
this.emitTrace("verification", "goal_verification_result", {
|
|
3892
3926
|
achieved: false,
|
|
3893
|
-
reason:
|
|
3894
|
-
missing:
|
|
3927
|
+
reason: verdict.reason,
|
|
3928
|
+
missing: verdict.missing
|
|
3895
3929
|
}, null, null, "error");
|
|
3896
3930
|
const warningBlock = `
|
|
3897
3931
|
|
|
@@ -3899,8 +3933,8 @@ ${blocks.join("\n\n")}
|
|
|
3899
3933
|
|
|
3900
3934
|
\u26A0\uFE0F **Goal Verification Warning**
|
|
3901
3935
|
* **Status:** Success criteria not fully met.
|
|
3902
|
-
* **Reason:** ${
|
|
3903
|
-
* **Missing:** ${
|
|
3936
|
+
* **Reason:** ${verdict.reason ?? "Unknown"}
|
|
3937
|
+
* **Missing:** ${verdict.missing ?? "Not specified"}
|
|
3904
3938
|
|
|
3905
3939
|
`;
|
|
3906
3940
|
const lastTurn = [...this.context.turns].reverse().find((t) => t.role === "assistant");
|