attocode 0.2.1 → 0.2.3
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 +191 -1
- package/README.md +7 -0
- package/dist/src/adapters.d.ts +6 -1
- package/dist/src/adapters.d.ts.map +1 -1
- package/dist/src/adapters.js +8 -1
- package/dist/src/adapters.js.map +1 -1
- package/dist/src/agent.d.ts +41 -4
- package/dist/src/agent.d.ts.map +1 -1
- package/dist/src/agent.js +846 -75
- package/dist/src/agent.js.map +1 -1
- package/dist/src/cli.d.ts.map +1 -1
- package/dist/src/cli.js +23 -2
- package/dist/src/cli.js.map +1 -1
- package/dist/src/core/protocol/types.d.ts +8 -8
- package/dist/src/defaults.d.ts +7 -2
- package/dist/src/defaults.d.ts.map +1 -1
- package/dist/src/defaults.js +38 -2
- package/dist/src/defaults.js.map +1 -1
- package/dist/src/integrations/agent-registry.d.ts +13 -0
- package/dist/src/integrations/agent-registry.d.ts.map +1 -1
- package/dist/src/integrations/agent-registry.js.map +1 -1
- package/dist/src/integrations/async-subagent.d.ts +135 -0
- package/dist/src/integrations/async-subagent.d.ts.map +1 -0
- package/dist/src/integrations/async-subagent.js +213 -0
- package/dist/src/integrations/async-subagent.js.map +1 -0
- package/dist/src/integrations/auto-checkpoint.d.ts +98 -0
- package/dist/src/integrations/auto-checkpoint.d.ts.map +1 -0
- package/dist/src/integrations/auto-checkpoint.js +252 -0
- package/dist/src/integrations/auto-checkpoint.js.map +1 -0
- package/dist/src/integrations/auto-compaction.d.ts.map +1 -1
- package/dist/src/integrations/auto-compaction.js +5 -1
- package/dist/src/integrations/auto-compaction.js.map +1 -1
- package/dist/src/integrations/bash-policy.d.ts +33 -0
- package/dist/src/integrations/bash-policy.d.ts.map +1 -0
- package/dist/src/integrations/bash-policy.js +142 -0
- package/dist/src/integrations/bash-policy.js.map +1 -0
- package/dist/src/integrations/codebase-context.d.ts +5 -0
- package/dist/src/integrations/codebase-context.d.ts.map +1 -1
- package/dist/src/integrations/codebase-context.js +33 -0
- package/dist/src/integrations/codebase-context.js.map +1 -1
- package/dist/src/integrations/complexity-classifier.d.ts +86 -0
- package/dist/src/integrations/complexity-classifier.d.ts.map +1 -0
- package/dist/src/integrations/complexity-classifier.js +233 -0
- package/dist/src/integrations/complexity-classifier.js.map +1 -0
- package/dist/src/integrations/delegation-protocol.d.ts +86 -0
- package/dist/src/integrations/delegation-protocol.d.ts.map +1 -0
- package/dist/src/integrations/delegation-protocol.js +127 -0
- package/dist/src/integrations/delegation-protocol.js.map +1 -0
- package/dist/src/integrations/dynamic-budget.d.ts +81 -0
- package/dist/src/integrations/dynamic-budget.d.ts.map +1 -0
- package/dist/src/integrations/dynamic-budget.js +151 -0
- package/dist/src/integrations/dynamic-budget.js.map +1 -0
- package/dist/src/integrations/economics.d.ts +86 -1
- package/dist/src/integrations/economics.d.ts.map +1 -1
- package/dist/src/integrations/economics.js +306 -11
- package/dist/src/integrations/economics.js.map +1 -1
- package/dist/src/integrations/environment-facts.d.ts +52 -0
- package/dist/src/integrations/environment-facts.d.ts.map +1 -0
- package/dist/src/integrations/environment-facts.js +84 -0
- package/dist/src/integrations/environment-facts.js.map +1 -0
- package/dist/src/integrations/hierarchical-config.d.ts.map +1 -1
- package/dist/src/integrations/hierarchical-config.js +17 -0
- package/dist/src/integrations/hierarchical-config.js.map +1 -1
- package/dist/src/integrations/index.d.ts +19 -2
- package/dist/src/integrations/index.d.ts.map +1 -1
- package/dist/src/integrations/index.js +34 -2
- package/dist/src/integrations/index.js.map +1 -1
- package/dist/src/integrations/injection-budget.d.ts +71 -0
- package/dist/src/integrations/injection-budget.d.ts.map +1 -0
- package/dist/src/integrations/injection-budget.js +136 -0
- package/dist/src/integrations/injection-budget.js.map +1 -0
- package/dist/src/integrations/mcp-client.d.ts.map +1 -1
- package/dist/src/integrations/mcp-client.js +14 -0
- package/dist/src/integrations/mcp-client.js.map +1 -1
- package/dist/src/integrations/mcp-custom-tools.d.ts +102 -0
- package/dist/src/integrations/mcp-custom-tools.d.ts.map +1 -0
- package/dist/src/integrations/mcp-custom-tools.js +232 -0
- package/dist/src/integrations/mcp-custom-tools.js.map +1 -0
- package/dist/src/integrations/mcp-tool-validator.d.ts +60 -0
- package/dist/src/integrations/mcp-tool-validator.d.ts.map +1 -0
- package/dist/src/integrations/mcp-tool-validator.js +141 -0
- package/dist/src/integrations/mcp-tool-validator.js.map +1 -0
- package/dist/src/integrations/policy-engine.d.ts +55 -0
- package/dist/src/integrations/policy-engine.d.ts.map +1 -0
- package/dist/src/integrations/policy-engine.js +247 -0
- package/dist/src/integrations/policy-engine.js.map +1 -0
- package/dist/src/integrations/safety.d.ts +5 -4
- package/dist/src/integrations/safety.d.ts.map +1 -1
- package/dist/src/integrations/safety.js +32 -7
- package/dist/src/integrations/safety.js.map +1 -1
- package/dist/src/integrations/sandbox/basic.d.ts +7 -0
- package/dist/src/integrations/sandbox/basic.d.ts.map +1 -1
- package/dist/src/integrations/sandbox/basic.js +27 -2
- package/dist/src/integrations/sandbox/basic.js.map +1 -1
- package/dist/src/integrations/sandbox/index.d.ts +6 -0
- package/dist/src/integrations/sandbox/index.d.ts.map +1 -1
- package/dist/src/integrations/sandbox/index.js +3 -0
- package/dist/src/integrations/sandbox/index.js.map +1 -1
- package/dist/src/integrations/sandbox/landlock.d.ts.map +1 -1
- package/dist/src/integrations/sandbox/landlock.js +3 -0
- package/dist/src/integrations/sandbox/landlock.js.map +1 -1
- package/dist/src/integrations/self-improvement.d.ts +90 -0
- package/dist/src/integrations/self-improvement.d.ts.map +1 -0
- package/dist/src/integrations/self-improvement.js +229 -0
- package/dist/src/integrations/self-improvement.js.map +1 -0
- package/dist/src/integrations/smart-decomposer.d.ts +22 -1
- package/dist/src/integrations/smart-decomposer.d.ts.map +1 -1
- package/dist/src/integrations/smart-decomposer.js +127 -28
- package/dist/src/integrations/smart-decomposer.js.map +1 -1
- package/dist/src/integrations/subagent-output-store.d.ts +91 -0
- package/dist/src/integrations/subagent-output-store.d.ts.map +1 -0
- package/dist/src/integrations/subagent-output-store.js +257 -0
- package/dist/src/integrations/subagent-output-store.js.map +1 -0
- package/dist/src/integrations/swarm/index.d.ts +2 -2
- package/dist/src/integrations/swarm/index.d.ts.map +1 -1
- package/dist/src/integrations/swarm/index.js +1 -1
- package/dist/src/integrations/swarm/index.js.map +1 -1
- package/dist/src/integrations/swarm/model-selector.d.ts +16 -0
- package/dist/src/integrations/swarm/model-selector.d.ts.map +1 -1
- package/dist/src/integrations/swarm/model-selector.js +123 -10
- package/dist/src/integrations/swarm/model-selector.js.map +1 -1
- package/dist/src/integrations/swarm/swarm-budget.d.ts +4 -0
- package/dist/src/integrations/swarm/swarm-budget.d.ts.map +1 -1
- package/dist/src/integrations/swarm/swarm-budget.js +6 -0
- package/dist/src/integrations/swarm/swarm-budget.js.map +1 -1
- package/dist/src/integrations/swarm/swarm-config-loader.d.ts +10 -1
- package/dist/src/integrations/swarm/swarm-config-loader.d.ts.map +1 -1
- package/dist/src/integrations/swarm/swarm-config-loader.js +226 -13
- package/dist/src/integrations/swarm/swarm-config-loader.js.map +1 -1
- package/dist/src/integrations/swarm/swarm-event-bridge.d.ts +12 -1
- package/dist/src/integrations/swarm/swarm-event-bridge.d.ts.map +1 -1
- package/dist/src/integrations/swarm/swarm-event-bridge.js +178 -9
- package/dist/src/integrations/swarm/swarm-event-bridge.js.map +1 -1
- package/dist/src/integrations/swarm/swarm-events.d.ts +66 -1
- package/dist/src/integrations/swarm/swarm-events.d.ts.map +1 -1
- package/dist/src/integrations/swarm/swarm-events.js +26 -5
- package/dist/src/integrations/swarm/swarm-events.js.map +1 -1
- package/dist/src/integrations/swarm/swarm-orchestrator.d.ts +127 -0
- package/dist/src/integrations/swarm/swarm-orchestrator.d.ts.map +1 -1
- package/dist/src/integrations/swarm/swarm-orchestrator.js +1842 -47
- package/dist/src/integrations/swarm/swarm-orchestrator.js.map +1 -1
- package/dist/src/integrations/swarm/swarm-quality-gate.d.ts +91 -3
- package/dist/src/integrations/swarm/swarm-quality-gate.d.ts.map +1 -1
- package/dist/src/integrations/swarm/swarm-quality-gate.js +395 -19
- package/dist/src/integrations/swarm/swarm-quality-gate.js.map +1 -1
- package/dist/src/integrations/swarm/task-queue.d.ts +55 -1
- package/dist/src/integrations/swarm/task-queue.d.ts.map +1 -1
- package/dist/src/integrations/swarm/task-queue.js +389 -16
- package/dist/src/integrations/swarm/task-queue.js.map +1 -1
- package/dist/src/integrations/swarm/types.d.ts +247 -11
- package/dist/src/integrations/swarm/types.d.ts.map +1 -1
- package/dist/src/integrations/swarm/types.js +67 -9
- package/dist/src/integrations/swarm/types.js.map +1 -1
- package/dist/src/integrations/swarm/worker-pool.d.ts +18 -5
- package/dist/src/integrations/swarm/worker-pool.d.ts.map +1 -1
- package/dist/src/integrations/swarm/worker-pool.js +236 -34
- package/dist/src/integrations/swarm/worker-pool.js.map +1 -1
- package/dist/src/integrations/thinking-strategy.d.ts +52 -0
- package/dist/src/integrations/thinking-strategy.d.ts.map +1 -0
- package/dist/src/integrations/thinking-strategy.js +129 -0
- package/dist/src/integrations/thinking-strategy.js.map +1 -0
- package/dist/src/integrations/tool-recommendation.d.ts +61 -0
- package/dist/src/integrations/tool-recommendation.d.ts.map +1 -0
- package/dist/src/integrations/tool-recommendation.js +268 -0
- package/dist/src/integrations/tool-recommendation.js.map +1 -0
- package/dist/src/integrations/verification-gate.d.ts +80 -0
- package/dist/src/integrations/verification-gate.d.ts.map +1 -0
- package/dist/src/integrations/verification-gate.js +146 -0
- package/dist/src/integrations/verification-gate.js.map +1 -0
- package/dist/src/integrations/work-log.d.ts +87 -0
- package/dist/src/integrations/work-log.d.ts.map +1 -0
- package/dist/src/integrations/work-log.js +275 -0
- package/dist/src/integrations/work-log.js.map +1 -0
- package/dist/src/main.js +31 -5
- package/dist/src/main.js.map +1 -1
- package/dist/src/modes/repl.d.ts.map +1 -1
- package/dist/src/modes/repl.js +10 -4
- package/dist/src/modes/repl.js.map +1 -1
- package/dist/src/modes/tui.d.ts.map +1 -1
- package/dist/src/modes/tui.js +5 -0
- package/dist/src/modes/tui.js.map +1 -1
- package/dist/src/modes.d.ts +6 -0
- package/dist/src/modes.d.ts.map +1 -1
- package/dist/src/modes.js +69 -21
- package/dist/src/modes.js.map +1 -1
- package/dist/src/tools/agent.d.ts.map +1 -1
- package/dist/src/tools/agent.js +11 -2
- package/dist/src/tools/agent.js.map +1 -1
- package/dist/src/tools/bash.d.ts +9 -3
- package/dist/src/tools/bash.d.ts.map +1 -1
- package/dist/src/tools/bash.js +12 -0
- package/dist/src/tools/bash.js.map +1 -1
- package/dist/src/tools/coercion.d.ts +6 -0
- package/dist/src/tools/coercion.d.ts.map +1 -1
- package/dist/src/tools/coercion.js +13 -0
- package/dist/src/tools/coercion.js.map +1 -1
- package/dist/src/tools/file.d.ts +2 -2
- package/dist/src/tools/file.js +2 -2
- package/dist/src/tools/file.js.map +1 -1
- package/dist/src/tools/permission.d.ts.map +1 -1
- package/dist/src/tools/permission.js +4 -111
- package/dist/src/tools/permission.js.map +1 -1
- package/dist/src/tools/standard.d.ts +17 -1
- package/dist/src/tools/standard.d.ts.map +1 -1
- package/dist/src/tools/standard.js +64 -11
- package/dist/src/tools/standard.js.map +1 -1
- package/dist/src/tracing/trace-collector.d.ts +167 -0
- package/dist/src/tracing/trace-collector.d.ts.map +1 -1
- package/dist/src/tracing/trace-collector.js +137 -0
- package/dist/src/tracing/trace-collector.js.map +1 -1
- package/dist/src/tracing/types.d.ts +105 -1
- package/dist/src/tracing/types.d.ts.map +1 -1
- package/dist/src/tracing/types.js.map +1 -1
- package/dist/src/tui/app.d.ts.map +1 -1
- package/dist/src/tui/app.js +34 -5
- package/dist/src/tui/app.js.map +1 -1
- package/dist/src/types.d.ts +89 -0
- package/dist/src/types.d.ts.map +1 -1
- package/package.json +6 -2
package/dist/src/agent.js
CHANGED
|
@@ -21,7 +21,12 @@
|
|
|
21
21
|
import { buildConfig, isFeatureEnabled, getEnabledFeatures, getSubagentTimeout, getSubagentMaxIterations, } from './defaults.js';
|
|
22
22
|
import { createModeManager, formatModeList, parseMode, calculateTaskSimilarity, SUBAGENT_PLAN_MODE_ADDITION, } from './modes.js';
|
|
23
23
|
import { createLSPFileTools, } from './agent-tools/index.js';
|
|
24
|
-
import { HookManager, MemoryManager, PlanningManager, ObservabilityManager, SafetyManager, RoutingManager, MultiAgentManager, ReActManager, ExecutionPolicyManager, ThreadManager, RulesManager, DEFAULT_RULE_SOURCES, ExecutionEconomicsManager, STANDARD_BUDGET, SUBAGENT_BUDGET, TIMEOUT_WRAPUP_PROMPT, AgentRegistry, filterToolsForAgent, formatAgentList, createCancellationManager, isCancellationError, createLinkedToken, createGracefulTimeout, race, createResourceManager, createLSPManager, createSemanticCacheManager, createSkillManager, formatSkillList, createContextEngineering, stableStringify, createCodebaseContext, buildContextFromChunks, createSharedFileCache, createBudgetPool, createPendingPlanManager, createInteractivePlanner, createRecursiveContext, createLearningStore, createCompactor, createAutoCompactionManager, createFileChangeTracker, createCapabilitiesRegistry, createSharedBlackboard, createTaskManager, createSwarmOrchestrator, createThrottledProvider, FREE_TIER_THROTTLE, PAID_TIER_THROTTLE,
|
|
24
|
+
import { HookManager, MemoryManager, PlanningManager, ObservabilityManager, SafetyManager, RoutingManager, MultiAgentManager, ReActManager, ExecutionPolicyManager, ThreadManager, RulesManager, DEFAULT_RULE_SOURCES, ExecutionEconomicsManager, STANDARD_BUDGET, SUBAGENT_BUDGET, TIMEOUT_WRAPUP_PROMPT, AgentRegistry, filterToolsForAgent, formatAgentList, createCancellationManager, isCancellationError, createLinkedToken, createGracefulTimeout, race, createResourceManager, createLSPManager, createSemanticCacheManager, createSkillManager, formatSkillList, createContextEngineering, stableStringify, createCodebaseContext, buildContextFromChunks, generateLightweightRepoMap, createSharedFileCache, createBudgetPool, createDynamicBudgetPool, createPendingPlanManager, createInteractivePlanner, createRecursiveContext, createLearningStore, createCompactor, createAutoCompactionManager, createFileChangeTracker, createCapabilitiesRegistry, createSharedBlackboard, createTaskManager, createSwarmOrchestrator, createThrottledProvider, FREE_TIER_THROTTLE, PAID_TIER_THROTTLE, createWorkLog, createVerificationGate,
|
|
25
|
+
// Phase 2: Orchestration
|
|
26
|
+
classifyComplexity, getScalingGuidance, buildDelegationPrompt, createMinimalDelegationSpec, getSubagentQualityPrompt, ToolRecommendationEngine, createToolRecommendationEngine, createInjectionBudgetManager,
|
|
27
|
+
// Phase 3: Advanced
|
|
28
|
+
getThinkingSystemPrompt, createSelfImprovementProtocol, createSubagentOutputStore, createSerperSearchTool, getEnvironmentFacts, formatFactsBlock, createAutoCheckpointManager, createSubagentSupervisor, createSubagentHandle, } from './integrations/index.js';
|
|
29
|
+
import { mergeApprovalScopeWithProfile, resolvePolicyProfile, } from './integrations/policy-engine.js';
|
|
25
30
|
// Lesson 26: Tracing & Evaluation integration
|
|
26
31
|
import { createTraceCollector } from './tracing/trace-collector.js';
|
|
27
32
|
// Model registry for context window limits
|
|
@@ -43,31 +48,107 @@ export const PARALLELIZABLE_TOOLS = new Set([
|
|
|
43
48
|
'search_code', 'get_file_info',
|
|
44
49
|
]);
|
|
45
50
|
/**
|
|
46
|
-
*
|
|
47
|
-
*
|
|
48
|
-
* Non-parallelizable tools break the sequence, starting a new batch.
|
|
51
|
+
* Tools that can run in parallel IF they target different files.
|
|
52
|
+
* write_file and edit_file on different paths are safe to parallelize.
|
|
49
53
|
*/
|
|
50
|
-
export
|
|
54
|
+
export const CONDITIONALLY_PARALLEL_TOOLS = new Set([
|
|
55
|
+
'write_file', 'edit_file',
|
|
56
|
+
]);
|
|
57
|
+
/**
|
|
58
|
+
* Extract the target file path from a tool call's arguments.
|
|
59
|
+
* Returns null if no file path can be determined.
|
|
60
|
+
*/
|
|
61
|
+
export function extractToolFilePath(toolCall) {
|
|
62
|
+
// Check common argument patterns
|
|
63
|
+
const args = toolCall;
|
|
64
|
+
for (const key of ['path', 'file_path', 'filename', 'file']) {
|
|
65
|
+
if (typeof args[key] === 'string')
|
|
66
|
+
return args[key];
|
|
67
|
+
}
|
|
68
|
+
// Check nested args object
|
|
69
|
+
if (args.args && typeof args.args === 'object') {
|
|
70
|
+
const nested = args.args;
|
|
71
|
+
for (const key of ['path', 'file_path', 'filename', 'file']) {
|
|
72
|
+
if (typeof nested[key] === 'string')
|
|
73
|
+
return nested[key];
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
// Check input object (common in structured tool calls)
|
|
77
|
+
if (args.input && typeof args.input === 'object') {
|
|
78
|
+
const input = args.input;
|
|
79
|
+
for (const key of ['path', 'file_path', 'filename', 'file']) {
|
|
80
|
+
if (typeof input[key] === 'string')
|
|
81
|
+
return input[key];
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
return null;
|
|
85
|
+
}
|
|
86
|
+
/**
|
|
87
|
+
* Check if a conditionally-parallel tool call conflicts with any tool
|
|
88
|
+
* in the current accumulator (same file path).
|
|
89
|
+
*/
|
|
90
|
+
function hasFileConflict(toolCall, accumulator) {
|
|
91
|
+
const path = extractToolFilePath(toolCall);
|
|
92
|
+
if (!path)
|
|
93
|
+
return true; // Can't determine path → assume conflict
|
|
94
|
+
for (const existing of accumulator) {
|
|
95
|
+
const existingPath = extractToolFilePath(existing);
|
|
96
|
+
if (existingPath === path)
|
|
97
|
+
return true; // Same file → conflict
|
|
98
|
+
}
|
|
99
|
+
return false;
|
|
100
|
+
}
|
|
101
|
+
/**
|
|
102
|
+
* Groups tool calls into batches for parallel/sequential execution.
|
|
103
|
+
* Uses accumulate-and-flush: parallelizable tools accumulate until a
|
|
104
|
+
* non-parallelizable tool flushes them as a batch. This produces optimal
|
|
105
|
+
* batching even for non-consecutive parallelizable tools.
|
|
106
|
+
*
|
|
107
|
+
* Enhanced with conditional parallelism: write_file/edit_file on
|
|
108
|
+
* DIFFERENT files can be batched together for parallel execution.
|
|
109
|
+
*
|
|
110
|
+
* Example: [read1, read2, write, read3, grep] → [[read1, read2], [write], [read3, grep]]
|
|
111
|
+
* (Previous algorithm produced 4 batches; this produces 3)
|
|
112
|
+
*
|
|
113
|
+
* Enhanced: [write_a, write_b, write_a] → [[write_a, write_b], [write_a]]
|
|
114
|
+
* (Different files parallelized, same file sequential)
|
|
115
|
+
*/
|
|
116
|
+
export function groupToolCallsIntoBatches(toolCalls, isParallelizable = (tc) => PARALLELIZABLE_TOOLS.has(tc.name), isConditionallyParallel = (tc) => CONDITIONALLY_PARALLEL_TOOLS.has(tc.name)) {
|
|
117
|
+
if (toolCalls.length === 0)
|
|
118
|
+
return [];
|
|
51
119
|
const batches = [];
|
|
52
|
-
let
|
|
53
|
-
let currentIsParallel = false;
|
|
120
|
+
let parallelAccum = [];
|
|
54
121
|
for (const toolCall of toolCalls) {
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
currentBatch.push(toolCall);
|
|
58
|
-
currentIsParallel = isParallel;
|
|
122
|
+
if (isParallelizable(toolCall)) {
|
|
123
|
+
parallelAccum.push(toolCall);
|
|
59
124
|
}
|
|
60
|
-
else if (
|
|
61
|
-
|
|
125
|
+
else if (isConditionallyParallel(toolCall)) {
|
|
126
|
+
// Can parallelize if no file conflict with existing accumulator
|
|
127
|
+
if (!hasFileConflict(toolCall, parallelAccum)) {
|
|
128
|
+
parallelAccum.push(toolCall);
|
|
129
|
+
}
|
|
130
|
+
else {
|
|
131
|
+
// Conflict: flush current batch, start new one with this tool
|
|
132
|
+
if (parallelAccum.length > 0) {
|
|
133
|
+
batches.push(parallelAccum);
|
|
134
|
+
parallelAccum = [];
|
|
135
|
+
}
|
|
136
|
+
parallelAccum.push(toolCall);
|
|
137
|
+
}
|
|
62
138
|
}
|
|
63
139
|
else {
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
140
|
+
// Flush any accumulated parallel tools as a single batch
|
|
141
|
+
if (parallelAccum.length > 0) {
|
|
142
|
+
batches.push(parallelAccum);
|
|
143
|
+
parallelAccum = [];
|
|
144
|
+
}
|
|
145
|
+
// Non-parallelizable tool gets its own batch
|
|
146
|
+
batches.push([toolCall]);
|
|
67
147
|
}
|
|
68
148
|
}
|
|
69
|
-
|
|
70
|
-
|
|
149
|
+
// Flush remaining parallel tools
|
|
150
|
+
if (parallelAccum.length > 0) {
|
|
151
|
+
batches.push(parallelAccum);
|
|
71
152
|
}
|
|
72
153
|
return batches;
|
|
73
154
|
}
|
|
@@ -99,6 +180,7 @@ export class ProductionAgent {
|
|
|
99
180
|
skillManager = null;
|
|
100
181
|
contextEngineering = null;
|
|
101
182
|
codebaseContext = null;
|
|
183
|
+
codebaseAnalysisTriggered = false;
|
|
102
184
|
traceCollector = null;
|
|
103
185
|
modeManager;
|
|
104
186
|
pendingPlanManager;
|
|
@@ -117,6 +199,15 @@ export class ProductionAgent {
|
|
|
117
199
|
taskManager = null;
|
|
118
200
|
store = null;
|
|
119
201
|
swarmOrchestrator = null;
|
|
202
|
+
workLog = null;
|
|
203
|
+
verificationGate = null;
|
|
204
|
+
// Phase 2-4 integration modules
|
|
205
|
+
injectionBudget = null;
|
|
206
|
+
selfImprovement = null;
|
|
207
|
+
subagentOutputStore = null;
|
|
208
|
+
autoCheckpointManager = null;
|
|
209
|
+
toolRecommendation = null;
|
|
210
|
+
lastComplexityAssessment = null;
|
|
120
211
|
// Duplicate spawn prevention - tracks recently spawned tasks to prevent doom loops
|
|
121
212
|
// Map<taskKey, { timestamp: number; result: string; queuedChanges: number }>
|
|
122
213
|
spawnedTasks = new Map();
|
|
@@ -132,6 +223,9 @@ export class ProductionAgent {
|
|
|
132
223
|
// Cacheable system prompt blocks for prompt caching (Improvement P1)
|
|
133
224
|
// When set, callLLM() will inject these as structured content with cache_control markers
|
|
134
225
|
cacheableSystemBlocks = null;
|
|
226
|
+
// Pre-compaction agentic turn: when true, the agent gets one more LLM turn
|
|
227
|
+
// to summarize its state before compaction clears the context.
|
|
228
|
+
compactionPending = false;
|
|
135
229
|
// Initialization tracking
|
|
136
230
|
initPromises = [];
|
|
137
231
|
initComplete = false;
|
|
@@ -250,7 +344,29 @@ export class ProductionAgent {
|
|
|
250
344
|
}
|
|
251
345
|
// Safety (Sandbox + Human-in-Loop)
|
|
252
346
|
if (isFeatureEnabled(this.config.sandbox) || isFeatureEnabled(this.config.humanInLoop)) {
|
|
253
|
-
this.safety = new SafetyManager(isFeatureEnabled(this.config.sandbox) ? this.config.sandbox : false, isFeatureEnabled(this.config.humanInLoop) ? this.config.humanInLoop : false);
|
|
347
|
+
this.safety = new SafetyManager(isFeatureEnabled(this.config.sandbox) ? this.config.sandbox : false, isFeatureEnabled(this.config.humanInLoop) ? this.config.humanInLoop : false, isFeatureEnabled(this.config.policyEngine) ? this.config.policyEngine : false);
|
|
348
|
+
}
|
|
349
|
+
if (isFeatureEnabled(this.config.policyEngine)) {
|
|
350
|
+
const rootPolicy = resolvePolicyProfile({
|
|
351
|
+
policyEngine: this.config.policyEngine,
|
|
352
|
+
sandboxConfig: isFeatureEnabled(this.config.sandbox) ? this.config.sandbox : undefined,
|
|
353
|
+
});
|
|
354
|
+
this.emit({
|
|
355
|
+
type: 'policy.profile.resolved',
|
|
356
|
+
profile: rootPolicy.profileName,
|
|
357
|
+
context: 'root',
|
|
358
|
+
selectionSource: rootPolicy.metadata.selectionSource,
|
|
359
|
+
usedLegacyMappings: rootPolicy.metadata.usedLegacyMappings,
|
|
360
|
+
legacySources: rootPolicy.metadata.legacyMappingSources,
|
|
361
|
+
});
|
|
362
|
+
if (rootPolicy.metadata.usedLegacyMappings) {
|
|
363
|
+
this.emit({
|
|
364
|
+
type: 'policy.legacy.fallback.used',
|
|
365
|
+
profile: rootPolicy.profileName,
|
|
366
|
+
sources: rootPolicy.metadata.legacyMappingSources,
|
|
367
|
+
warnings: rootPolicy.metadata.warnings,
|
|
368
|
+
});
|
|
369
|
+
}
|
|
254
370
|
}
|
|
255
371
|
// Routing
|
|
256
372
|
if (isFeatureEnabled(this.config.routing)) {
|
|
@@ -311,6 +427,19 @@ export class ProductionAgent {
|
|
|
311
427
|
maxIterations: this.config.maxIterations,
|
|
312
428
|
targetIterations: Math.min(baseBudget.targetIterations ?? 20, this.config.maxIterations),
|
|
313
429
|
});
|
|
430
|
+
// Work Log - compaction-resilient summary of agent work
|
|
431
|
+
// Always enabled - minimal overhead and critical for long-running tasks
|
|
432
|
+
this.workLog = createWorkLog();
|
|
433
|
+
// Verification Gate - opt-in completion verification
|
|
434
|
+
if (this.config.verificationCriteria) {
|
|
435
|
+
this.verificationGate = createVerificationGate(this.config.verificationCriteria);
|
|
436
|
+
}
|
|
437
|
+
// Phase 2-4: Orchestration & Advanced modules (always enabled, lightweight)
|
|
438
|
+
this.injectionBudget = createInjectionBudgetManager();
|
|
439
|
+
this.selfImprovement = createSelfImprovementProtocol(undefined, this.learningStore ?? undefined);
|
|
440
|
+
this.subagentOutputStore = createSubagentOutputStore({ persistToFile: false });
|
|
441
|
+
this.autoCheckpointManager = createAutoCheckpointManager({ enabled: true });
|
|
442
|
+
this.toolRecommendation = createToolRecommendationEngine();
|
|
314
443
|
// Agent Registry - always enabled for subagent support
|
|
315
444
|
this.agentRegistry = new AgentRegistry();
|
|
316
445
|
// Load user agents asynchronously - tracked for ensureReady()
|
|
@@ -341,6 +470,15 @@ export class ProductionAgent {
|
|
|
341
470
|
for (const tool of taskTools) {
|
|
342
471
|
this.tools.set(tool.name, tool);
|
|
343
472
|
}
|
|
473
|
+
// Built-in web search (Serper API) — gracefully handles missing API key
|
|
474
|
+
const serperCustomTool = createSerperSearchTool();
|
|
475
|
+
this.tools.set('web_search', {
|
|
476
|
+
name: serperCustomTool.name,
|
|
477
|
+
description: serperCustomTool.description,
|
|
478
|
+
parameters: serperCustomTool.inputSchema,
|
|
479
|
+
execute: serperCustomTool.execute,
|
|
480
|
+
dangerLevel: 'safe',
|
|
481
|
+
});
|
|
344
482
|
// Swarm Mode (experimental)
|
|
345
483
|
if (this.config.swarm) {
|
|
346
484
|
const swarmConfig = this.config.swarm;
|
|
@@ -832,11 +970,19 @@ export class ProductionAgent {
|
|
|
832
970
|
else {
|
|
833
971
|
// Single-task mode (backward compatibility) - start session with task
|
|
834
972
|
const traceSessionId = `session-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`;
|
|
835
|
-
|
|
973
|
+
const sessionMetadata = {};
|
|
974
|
+
if (this.swarmOrchestrator) {
|
|
975
|
+
sessionMetadata.swarm = true;
|
|
976
|
+
}
|
|
977
|
+
await this.traceCollector?.startSession(traceSessionId, task, this.config.model || 'default', sessionMetadata);
|
|
836
978
|
}
|
|
837
979
|
try {
|
|
838
980
|
// Check for cancellation before starting
|
|
839
981
|
cancellationToken?.throwIfCancellationRequested();
|
|
982
|
+
// Classify task complexity for scaling guidance
|
|
983
|
+
this.lastComplexityAssessment = classifyComplexity(task, {
|
|
984
|
+
hasActivePlan: !!this.state.plan,
|
|
985
|
+
});
|
|
840
986
|
// Check if swarm mode should handle this task
|
|
841
987
|
if (this.swarmOrchestrator) {
|
|
842
988
|
const swarmResult = await this.runSwarm(task);
|
|
@@ -987,6 +1133,161 @@ export class ProductionAgent {
|
|
|
987
1133
|
const { SwarmEventBridge } = await import('./integrations/swarm/swarm-event-bridge.js');
|
|
988
1134
|
const bridge = new SwarmEventBridge({ outputDir: '.agent/swarm-live' });
|
|
989
1135
|
const unsubBridge = bridge.attach(this.swarmOrchestrator);
|
|
1136
|
+
// Bridge swarm events into JSONL trace pipeline
|
|
1137
|
+
const traceCollector = this.traceCollector;
|
|
1138
|
+
let unsubTrace;
|
|
1139
|
+
if (traceCollector) {
|
|
1140
|
+
unsubTrace = this.swarmOrchestrator.subscribe(event => {
|
|
1141
|
+
switch (event.type) {
|
|
1142
|
+
case 'swarm.start':
|
|
1143
|
+
traceCollector.record({
|
|
1144
|
+
type: 'swarm.start',
|
|
1145
|
+
data: { taskCount: event.taskCount, config: event.config },
|
|
1146
|
+
});
|
|
1147
|
+
break;
|
|
1148
|
+
case 'swarm.tasks.loaded':
|
|
1149
|
+
traceCollector.record({
|
|
1150
|
+
type: 'swarm.decomposition',
|
|
1151
|
+
data: {
|
|
1152
|
+
tasks: event.tasks.map(t => ({
|
|
1153
|
+
id: t.id,
|
|
1154
|
+
description: t.description.slice(0, 200),
|
|
1155
|
+
type: t.type,
|
|
1156
|
+
wave: t.wave,
|
|
1157
|
+
deps: t.dependencies,
|
|
1158
|
+
})),
|
|
1159
|
+
totalWaves: Math.max(...event.tasks.map(t => t.wave), 0) + 1,
|
|
1160
|
+
},
|
|
1161
|
+
});
|
|
1162
|
+
break;
|
|
1163
|
+
case 'swarm.wave.start':
|
|
1164
|
+
traceCollector.record({
|
|
1165
|
+
type: 'swarm.wave',
|
|
1166
|
+
data: { phase: 'start', wave: event.wave, taskCount: event.taskCount },
|
|
1167
|
+
});
|
|
1168
|
+
break;
|
|
1169
|
+
case 'swarm.wave.complete':
|
|
1170
|
+
traceCollector.record({
|
|
1171
|
+
type: 'swarm.wave',
|
|
1172
|
+
data: {
|
|
1173
|
+
phase: 'complete',
|
|
1174
|
+
wave: event.wave,
|
|
1175
|
+
taskCount: event.completed + event.failed + (event.skipped ?? 0),
|
|
1176
|
+
completed: event.completed,
|
|
1177
|
+
failed: event.failed,
|
|
1178
|
+
},
|
|
1179
|
+
});
|
|
1180
|
+
break;
|
|
1181
|
+
case 'swarm.task.dispatched':
|
|
1182
|
+
traceCollector.record({
|
|
1183
|
+
type: 'swarm.task',
|
|
1184
|
+
data: { phase: 'dispatched', taskId: event.taskId, model: event.model },
|
|
1185
|
+
});
|
|
1186
|
+
break;
|
|
1187
|
+
case 'swarm.task.completed':
|
|
1188
|
+
traceCollector.record({
|
|
1189
|
+
type: 'swarm.task',
|
|
1190
|
+
data: {
|
|
1191
|
+
phase: 'completed',
|
|
1192
|
+
taskId: event.taskId,
|
|
1193
|
+
tokensUsed: event.tokensUsed,
|
|
1194
|
+
costUsed: event.costUsed,
|
|
1195
|
+
qualityScore: event.qualityScore,
|
|
1196
|
+
},
|
|
1197
|
+
});
|
|
1198
|
+
break;
|
|
1199
|
+
case 'swarm.task.failed':
|
|
1200
|
+
traceCollector.record({
|
|
1201
|
+
type: 'swarm.task',
|
|
1202
|
+
data: { phase: 'failed', taskId: event.taskId, error: event.error },
|
|
1203
|
+
});
|
|
1204
|
+
break;
|
|
1205
|
+
case 'swarm.task.skipped':
|
|
1206
|
+
traceCollector.record({
|
|
1207
|
+
type: 'swarm.task',
|
|
1208
|
+
data: { phase: 'skipped', taskId: event.taskId, reason: event.reason },
|
|
1209
|
+
});
|
|
1210
|
+
break;
|
|
1211
|
+
case 'swarm.quality.rejected':
|
|
1212
|
+
traceCollector.record({
|
|
1213
|
+
type: 'swarm.quality',
|
|
1214
|
+
data: { taskId: event.taskId, score: event.score, feedback: event.feedback },
|
|
1215
|
+
});
|
|
1216
|
+
break;
|
|
1217
|
+
case 'swarm.budget.update':
|
|
1218
|
+
traceCollector.record({
|
|
1219
|
+
type: 'swarm.budget',
|
|
1220
|
+
data: {
|
|
1221
|
+
tokensUsed: event.tokensUsed,
|
|
1222
|
+
tokensTotal: event.tokensTotal,
|
|
1223
|
+
costUsed: event.costUsed,
|
|
1224
|
+
costTotal: event.costTotal,
|
|
1225
|
+
},
|
|
1226
|
+
});
|
|
1227
|
+
break;
|
|
1228
|
+
case 'swarm.verify.start':
|
|
1229
|
+
traceCollector.record({
|
|
1230
|
+
type: 'swarm.verification',
|
|
1231
|
+
data: { phase: 'start', description: `${event.stepCount} verification steps` },
|
|
1232
|
+
});
|
|
1233
|
+
break;
|
|
1234
|
+
case 'swarm.verify.step':
|
|
1235
|
+
traceCollector.record({
|
|
1236
|
+
type: 'swarm.verification',
|
|
1237
|
+
data: {
|
|
1238
|
+
phase: 'step',
|
|
1239
|
+
stepIndex: event.stepIndex,
|
|
1240
|
+
description: event.description,
|
|
1241
|
+
passed: event.passed,
|
|
1242
|
+
},
|
|
1243
|
+
});
|
|
1244
|
+
break;
|
|
1245
|
+
case 'swarm.verify.complete':
|
|
1246
|
+
traceCollector.record({
|
|
1247
|
+
type: 'swarm.verification',
|
|
1248
|
+
data: {
|
|
1249
|
+
phase: 'complete',
|
|
1250
|
+
passed: event.result.passed,
|
|
1251
|
+
summary: event.result.summary,
|
|
1252
|
+
},
|
|
1253
|
+
});
|
|
1254
|
+
break;
|
|
1255
|
+
case 'swarm.orchestrator.llm':
|
|
1256
|
+
traceCollector.record({
|
|
1257
|
+
type: 'swarm.orchestrator.llm',
|
|
1258
|
+
data: { model: event.model, purpose: event.purpose, tokens: event.tokens, cost: event.cost },
|
|
1259
|
+
});
|
|
1260
|
+
break;
|
|
1261
|
+
case 'swarm.wave.allFailed':
|
|
1262
|
+
traceCollector.record({
|
|
1263
|
+
type: 'swarm.wave.allFailed',
|
|
1264
|
+
data: { wave: event.wave },
|
|
1265
|
+
});
|
|
1266
|
+
break;
|
|
1267
|
+
case 'swarm.phase.progress':
|
|
1268
|
+
traceCollector.record({
|
|
1269
|
+
type: 'swarm.phase.progress',
|
|
1270
|
+
data: { phase: event.phase, message: event.message },
|
|
1271
|
+
});
|
|
1272
|
+
break;
|
|
1273
|
+
case 'swarm.complete':
|
|
1274
|
+
traceCollector.record({
|
|
1275
|
+
type: 'swarm.complete',
|
|
1276
|
+
data: {
|
|
1277
|
+
stats: {
|
|
1278
|
+
totalTasks: event.stats.totalTasks,
|
|
1279
|
+
completedTasks: event.stats.completedTasks,
|
|
1280
|
+
failedTasks: event.stats.failedTasks,
|
|
1281
|
+
totalTokens: event.stats.totalTokens,
|
|
1282
|
+
totalCost: event.stats.totalCost,
|
|
1283
|
+
totalDuration: event.stats.totalDurationMs,
|
|
1284
|
+
},
|
|
1285
|
+
},
|
|
1286
|
+
});
|
|
1287
|
+
break;
|
|
1288
|
+
}
|
|
1289
|
+
});
|
|
1290
|
+
}
|
|
990
1291
|
try {
|
|
991
1292
|
const result = await this.swarmOrchestrator.execute(task);
|
|
992
1293
|
// Populate task DAG for dashboard after execution
|
|
@@ -1001,6 +1302,7 @@ export class ProductionAgent {
|
|
|
1001
1302
|
return result;
|
|
1002
1303
|
}
|
|
1003
1304
|
finally {
|
|
1305
|
+
unsubTrace?.();
|
|
1004
1306
|
unsubBridge();
|
|
1005
1307
|
bridge.close();
|
|
1006
1308
|
unsubSwarm();
|
|
@@ -1119,6 +1421,14 @@ export class ProductionAgent {
|
|
|
1119
1421
|
content: `[CONTEXT REDUCED: Earlier messages were removed to stay within budget. Conversation continues from recent context.]`,
|
|
1120
1422
|
});
|
|
1121
1423
|
messages.push(...recentMessages);
|
|
1424
|
+
// Inject work log after emergency truncation to prevent amnesia
|
|
1425
|
+
if (this.workLog?.hasContent()) {
|
|
1426
|
+
const workLogMessage = {
|
|
1427
|
+
role: 'user',
|
|
1428
|
+
content: this.workLog.toCompactString(),
|
|
1429
|
+
};
|
|
1430
|
+
messages.push(workLogMessage);
|
|
1431
|
+
}
|
|
1122
1432
|
// Update state messages too
|
|
1123
1433
|
this.state.messages.length = 0;
|
|
1124
1434
|
this.state.messages.push(...messages);
|
|
@@ -1299,6 +1609,35 @@ export class ProductionAgent {
|
|
|
1299
1609
|
}
|
|
1300
1610
|
}
|
|
1301
1611
|
// =====================================================================
|
|
1612
|
+
// INJECTION BUDGET ANALYSIS (Phase 2 - monitoring mode)
|
|
1613
|
+
// Collects stats on context injections without gating; logs when
|
|
1614
|
+
// budget would have dropped items. Validates system before enabling gating.
|
|
1615
|
+
// =====================================================================
|
|
1616
|
+
if (this.injectionBudget) {
|
|
1617
|
+
const proposals = [];
|
|
1618
|
+
if (budgetInjectedPrompt) {
|
|
1619
|
+
proposals.push({ name: 'budget_warning', priority: 0, maxTokens: 500, content: budgetInjectedPrompt });
|
|
1620
|
+
}
|
|
1621
|
+
// Approximate recitation content (actual injection handled above)
|
|
1622
|
+
if (this.contextEngineering) {
|
|
1623
|
+
const failureCtx = this.contextEngineering.getFailureContext(5);
|
|
1624
|
+
if (failureCtx) {
|
|
1625
|
+
proposals.push({ name: 'failure_context', priority: 2, maxTokens: 300, content: failureCtx });
|
|
1626
|
+
}
|
|
1627
|
+
}
|
|
1628
|
+
if (proposals.length > 0) {
|
|
1629
|
+
const accepted = this.injectionBudget.allocate(proposals);
|
|
1630
|
+
const stats = this.injectionBudget.getLastStats();
|
|
1631
|
+
if (stats && stats.droppedNames.length > 0 && process.env.DEBUG) {
|
|
1632
|
+
console.log(`[injection-budget] Would drop: ${stats.droppedNames.join(', ')} (${stats.proposedTokens} proposed, ${stats.acceptedTokens} accepted)`);
|
|
1633
|
+
}
|
|
1634
|
+
// Log total injection overhead for observability
|
|
1635
|
+
if (stats && process.env.DEBUG_LLM) {
|
|
1636
|
+
console.log(`[injection-budget] Iteration ${this.state.iteration}: ${accepted.length}/${proposals.length} injections, ~${stats.acceptedTokens} tokens`);
|
|
1637
|
+
}
|
|
1638
|
+
}
|
|
1639
|
+
}
|
|
1640
|
+
// =====================================================================
|
|
1302
1641
|
// RESILIENT LLM CALL: Empty response retries + max_tokens continuation
|
|
1303
1642
|
// =====================================================================
|
|
1304
1643
|
// Get resilience config
|
|
@@ -1594,6 +1933,24 @@ export class ProductionAgent {
|
|
|
1594
1933
|
});
|
|
1595
1934
|
incompleteActionRetries = 0;
|
|
1596
1935
|
}
|
|
1936
|
+
// Verification gate: if criteria not met, nudge agent to verify before completing
|
|
1937
|
+
if (this.verificationGate && !forceTextOnly) {
|
|
1938
|
+
const vResult = this.verificationGate.check();
|
|
1939
|
+
if (!vResult.satisfied && !vResult.forceAllow && vResult.nudge) {
|
|
1940
|
+
// Inject nudge and continue the loop
|
|
1941
|
+
const nudgeMessage = {
|
|
1942
|
+
role: 'user',
|
|
1943
|
+
content: vResult.nudge,
|
|
1944
|
+
};
|
|
1945
|
+
messages.push(nudgeMessage);
|
|
1946
|
+
this.state.messages.push(nudgeMessage);
|
|
1947
|
+
this.observability?.logger?.info('Verification gate nudge', {
|
|
1948
|
+
missing: vResult.missing,
|
|
1949
|
+
nudgeCount: this.verificationGate.getState().nudgeCount,
|
|
1950
|
+
});
|
|
1951
|
+
continue;
|
|
1952
|
+
}
|
|
1953
|
+
}
|
|
1597
1954
|
// No tool calls (or forced to ignore), agent is done - compact tool outputs to save context
|
|
1598
1955
|
// The model has "consumed" the tool outputs and produced a response,
|
|
1599
1956
|
// so we can replace verbose outputs with compact summaries
|
|
@@ -1630,12 +1987,33 @@ export class ProductionAgent {
|
|
|
1630
1987
|
// Execute tool calls (we know toolCalls is defined here due to the check above)
|
|
1631
1988
|
const toolCalls = response.toolCalls;
|
|
1632
1989
|
const toolResults = await this.executeToolCalls(toolCalls);
|
|
1633
|
-
// Record tool calls for economics/progress tracking
|
|
1990
|
+
// Record tool calls for economics/progress tracking + work log
|
|
1634
1991
|
for (let i = 0; i < toolCalls.length; i++) {
|
|
1635
1992
|
const toolCall = toolCalls[i];
|
|
1636
1993
|
const result = toolResults[i];
|
|
1637
1994
|
executedToolNames.add(toolCall.name);
|
|
1638
1995
|
this.economics?.recordToolCall(toolCall.name, toolCall.arguments, result?.result);
|
|
1996
|
+
// Record in work log for compaction resilience
|
|
1997
|
+
const toolOutput = result?.result && typeof result.result === 'object' && 'output' in result.result
|
|
1998
|
+
? String(result.result.output)
|
|
1999
|
+
: typeof result?.result === 'string' ? result.result : undefined;
|
|
2000
|
+
this.workLog?.recordToolExecution(toolCall.name, toolCall.arguments, toolOutput);
|
|
2001
|
+
// Record in verification gate
|
|
2002
|
+
if (this.verificationGate) {
|
|
2003
|
+
if (toolCall.name === 'bash') {
|
|
2004
|
+
const toolRes = result?.result;
|
|
2005
|
+
const output = toolRes && typeof toolRes === 'object' && 'output' in toolRes
|
|
2006
|
+
? String(toolRes.output)
|
|
2007
|
+
: typeof toolRes === 'string' ? toolRes : '';
|
|
2008
|
+
const exitCode = toolRes && typeof toolRes === 'object' && toolRes.metadata
|
|
2009
|
+
? toolRes.metadata.exitCode ?? null
|
|
2010
|
+
: null;
|
|
2011
|
+
this.verificationGate.recordBashExecution(String(toolCall.arguments.command || ''), output, exitCode);
|
|
2012
|
+
}
|
|
2013
|
+
if (['write_file', 'edit_file'].includes(toolCall.name)) {
|
|
2014
|
+
this.verificationGate.recordFileChange();
|
|
2015
|
+
}
|
|
2016
|
+
}
|
|
1639
2017
|
}
|
|
1640
2018
|
// Add tool results to messages (with truncation and proactive budget management)
|
|
1641
2019
|
const MAX_TOOL_OUTPUT_CHARS = 8000; // ~2000 tokens max per tool output
|
|
@@ -1652,11 +2030,100 @@ export class ProductionAgent {
|
|
|
1652
2030
|
});
|
|
1653
2031
|
// Handle compaction result
|
|
1654
2032
|
if (compactionResult.status === 'compacted' && compactionResult.compactedMessages) {
|
|
1655
|
-
//
|
|
1656
|
-
|
|
1657
|
-
|
|
1658
|
-
|
|
1659
|
-
|
|
2033
|
+
// ─── Pre-compaction agentic turn ───────────────────────────────
|
|
2034
|
+
// Give the agent one LLM turn to summarize critical state before
|
|
2035
|
+
// compaction clears the context. On the first trigger we inject a
|
|
2036
|
+
// system message and skip compaction; on the next trigger (the
|
|
2037
|
+
// agent has already responded) we proceed with actual compaction.
|
|
2038
|
+
if (!this.compactionPending) {
|
|
2039
|
+
this.compactionPending = true;
|
|
2040
|
+
const preCompactionMsg = {
|
|
2041
|
+
role: 'user',
|
|
2042
|
+
content: '[SYSTEM] Context compaction is imminent. Summarize your current progress, key findings, and next steps into a single concise message. This will be preserved after compaction.',
|
|
2043
|
+
};
|
|
2044
|
+
messages.push(preCompactionMsg);
|
|
2045
|
+
this.state.messages.push(preCompactionMsg);
|
|
2046
|
+
this.observability?.logger?.info('Pre-compaction agentic turn: injected summary request');
|
|
2047
|
+
// Skip compaction this iteration — let the agent respond first
|
|
2048
|
+
// (continue to tool result processing below)
|
|
2049
|
+
}
|
|
2050
|
+
else {
|
|
2051
|
+
// Agent has had its chance to summarize — now compact for real
|
|
2052
|
+
this.compactionPending = false;
|
|
2053
|
+
// Pre-compaction checkpoint: save full state before discarding
|
|
2054
|
+
try {
|
|
2055
|
+
this.autoCheckpoint(true); // force=true bypasses frequency check
|
|
2056
|
+
}
|
|
2057
|
+
catch {
|
|
2058
|
+
// Non-critical — don't block compaction
|
|
2059
|
+
}
|
|
2060
|
+
// Replace messages with compacted version
|
|
2061
|
+
messages.length = 0;
|
|
2062
|
+
messages.push(...compactionResult.compactedMessages);
|
|
2063
|
+
this.state.messages.length = 0;
|
|
2064
|
+
this.state.messages.push(...compactionResult.compactedMessages);
|
|
2065
|
+
// Inject work log after compaction to prevent amnesia
|
|
2066
|
+
if (this.workLog?.hasContent()) {
|
|
2067
|
+
const workLogMessage = {
|
|
2068
|
+
role: 'user',
|
|
2069
|
+
content: this.workLog.toCompactString(),
|
|
2070
|
+
};
|
|
2071
|
+
messages.push(workLogMessage);
|
|
2072
|
+
this.state.messages.push(workLogMessage);
|
|
2073
|
+
}
|
|
2074
|
+
// Context recovery: re-inject critical state after compaction
|
|
2075
|
+
const recoveryParts = [];
|
|
2076
|
+
// Goals
|
|
2077
|
+
if (this.store) {
|
|
2078
|
+
const goalsSummary = this.store.getGoalsSummary();
|
|
2079
|
+
if (goalsSummary && goalsSummary !== 'No active goals.' && goalsSummary !== 'Goals feature not available.') {
|
|
2080
|
+
recoveryParts.push(goalsSummary);
|
|
2081
|
+
}
|
|
2082
|
+
}
|
|
2083
|
+
// Junctures (last 5 key moments)
|
|
2084
|
+
if (this.store) {
|
|
2085
|
+
const juncturesSummary = this.store.getJuncturesSummary(undefined, 5);
|
|
2086
|
+
if (juncturesSummary) {
|
|
2087
|
+
recoveryParts.push(juncturesSummary);
|
|
2088
|
+
}
|
|
2089
|
+
}
|
|
2090
|
+
// Learnings from past patterns
|
|
2091
|
+
if (this.learningStore) {
|
|
2092
|
+
const learnings = this.learningStore.getLearningContext({ maxLearnings: 3 });
|
|
2093
|
+
if (learnings) {
|
|
2094
|
+
recoveryParts.push(learnings);
|
|
2095
|
+
}
|
|
2096
|
+
}
|
|
2097
|
+
if (recoveryParts.length > 0) {
|
|
2098
|
+
const recoveryMessage = {
|
|
2099
|
+
role: 'user',
|
|
2100
|
+
content: `[CONTEXT RECOVERY — Re-injected after compaction]\n\n${recoveryParts.join('\n\n')}`,
|
|
2101
|
+
};
|
|
2102
|
+
messages.push(recoveryMessage);
|
|
2103
|
+
this.state.messages.push(recoveryMessage);
|
|
2104
|
+
}
|
|
2105
|
+
// Emit compaction event for observability
|
|
2106
|
+
const compactionTokensAfter = this.estimateContextTokens(messages);
|
|
2107
|
+
const compactionRecoveryInjected = recoveryParts.length > 0;
|
|
2108
|
+
const compactionEvent = {
|
|
2109
|
+
type: 'context.compacted',
|
|
2110
|
+
tokensBefore: currentContextTokens,
|
|
2111
|
+
tokensAfter: compactionTokensAfter,
|
|
2112
|
+
recoveryInjected: compactionRecoveryInjected,
|
|
2113
|
+
};
|
|
2114
|
+
this.emit(compactionEvent);
|
|
2115
|
+
// Record to trace collector for JSONL output
|
|
2116
|
+
if (this.traceCollector) {
|
|
2117
|
+
this.traceCollector.record({
|
|
2118
|
+
type: 'context.compacted',
|
|
2119
|
+
data: {
|
|
2120
|
+
tokensBefore: currentContextTokens,
|
|
2121
|
+
tokensAfter: compactionTokensAfter,
|
|
2122
|
+
recoveryInjected: compactionRecoveryInjected,
|
|
2123
|
+
},
|
|
2124
|
+
});
|
|
2125
|
+
}
|
|
2126
|
+
}
|
|
1660
2127
|
}
|
|
1661
2128
|
else if (compactionResult.status === 'hard_limit') {
|
|
1662
2129
|
// Hard limit reached - this is serious, emit error
|
|
@@ -1679,6 +2146,13 @@ export class ProductionAgent {
|
|
|
1679
2146
|
currentTokens: currentUsage.tokens,
|
|
1680
2147
|
maxTokens: budget.maxTokens,
|
|
1681
2148
|
});
|
|
2149
|
+
// Also checkpoint before fallback compaction
|
|
2150
|
+
try {
|
|
2151
|
+
this.autoCheckpoint(true);
|
|
2152
|
+
}
|
|
2153
|
+
catch {
|
|
2154
|
+
// Non-critical
|
|
2155
|
+
}
|
|
1682
2156
|
this.compactToolOutputs();
|
|
1683
2157
|
}
|
|
1684
2158
|
}
|
|
@@ -1688,8 +2162,10 @@ export class ProductionAgent {
|
|
|
1688
2162
|
const sourceToolName = toolCallNameById.get(result.callId);
|
|
1689
2163
|
const isExpensiveResult = sourceToolName === 'spawn_agent' || sourceToolName === 'spawn_agents_parallel';
|
|
1690
2164
|
// Truncate long outputs to save context
|
|
1691
|
-
|
|
1692
|
-
|
|
2165
|
+
// Use larger limit for subagent results to preserve critical context
|
|
2166
|
+
const effectiveMaxChars = isExpensiveResult ? MAX_TOOL_OUTPUT_CHARS * 2 : MAX_TOOL_OUTPUT_CHARS;
|
|
2167
|
+
if (content.length > effectiveMaxChars) {
|
|
2168
|
+
content = content.slice(0, effectiveMaxChars) + `\n\n... [truncated ${content.length - effectiveMaxChars} chars]`;
|
|
1693
2169
|
}
|
|
1694
2170
|
// =======================================================================
|
|
1695
2171
|
// ESTIMATE if adding this result would exceed budget
|
|
@@ -1813,10 +2289,14 @@ export class ProductionAgent {
|
|
|
1813
2289
|
const reservedTokens = 10500;
|
|
1814
2290
|
const maxContextTokens = (this.config.maxContextTokens ?? 80000) - reservedTokens;
|
|
1815
2291
|
const codebaseBudget = Math.min(maxContextTokens * 0.3, 15000); // Up to 30% or 15K tokens
|
|
1816
|
-
|
|
1817
|
-
|
|
1818
|
-
|
|
1819
|
-
|
|
2292
|
+
const repoMap = this.codebaseContext.getRepoMap();
|
|
2293
|
+
// Lazy: trigger analysis on first system prompt build, ready by next turn
|
|
2294
|
+
if (!repoMap && !this.codebaseAnalysisTriggered) {
|
|
2295
|
+
this.codebaseAnalysisTriggered = true;
|
|
2296
|
+
this.codebaseContext.analyze().catch(() => { });
|
|
2297
|
+
}
|
|
2298
|
+
if (repoMap) {
|
|
2299
|
+
try {
|
|
1820
2300
|
const selection = this.selectRelevantCodeSync(task, codebaseBudget);
|
|
1821
2301
|
if (selection.chunks.length > 0) {
|
|
1822
2302
|
codebaseContextStr = buildContextFromChunks(selection.chunks, {
|
|
@@ -1825,10 +2305,14 @@ export class ProductionAgent {
|
|
|
1825
2305
|
maxTotalTokens: codebaseBudget,
|
|
1826
2306
|
});
|
|
1827
2307
|
}
|
|
2308
|
+
else {
|
|
2309
|
+
// Fallback: lightweight repo map when task-specific selection finds nothing
|
|
2310
|
+
codebaseContextStr = generateLightweightRepoMap(repoMap, codebaseBudget);
|
|
2311
|
+
}
|
|
2312
|
+
}
|
|
2313
|
+
catch {
|
|
2314
|
+
// Selection error — skip
|
|
1828
2315
|
}
|
|
1829
|
-
}
|
|
1830
|
-
catch {
|
|
1831
|
-
// Codebase analysis not ready yet - skip for this call
|
|
1832
2316
|
}
|
|
1833
2317
|
}
|
|
1834
2318
|
// Build tool descriptions
|
|
@@ -1851,12 +2335,25 @@ export class ProductionAgent {
|
|
|
1851
2335
|
}
|
|
1852
2336
|
}
|
|
1853
2337
|
// Build system prompt using cache-aware builder if available (Trick P)
|
|
1854
|
-
// Combine memory, learnings, and
|
|
1855
|
-
const
|
|
2338
|
+
// Combine memory, learnings, codebase context, and environment facts
|
|
2339
|
+
const combinedContextParts = [
|
|
2340
|
+
// Environment facts — temporal/platform grounding (prevents stale date hallucinations)
|
|
2341
|
+
formatFactsBlock(getEnvironmentFacts()),
|
|
1856
2342
|
...(memoryContext.length > 0 ? memoryContext : []),
|
|
1857
2343
|
...(learningsContext ? [learningsContext] : []),
|
|
1858
2344
|
...(codebaseContextStr ? [`\n## Relevant Code\n${codebaseContextStr}`] : []),
|
|
1859
|
-
]
|
|
2345
|
+
];
|
|
2346
|
+
// Inject thinking directives and scaling guidance for non-simple tasks
|
|
2347
|
+
if (this.lastComplexityAssessment) {
|
|
2348
|
+
const thinkingPrompt = getThinkingSystemPrompt(this.lastComplexityAssessment.tier);
|
|
2349
|
+
if (thinkingPrompt) {
|
|
2350
|
+
combinedContextParts.push(thinkingPrompt);
|
|
2351
|
+
}
|
|
2352
|
+
if (this.lastComplexityAssessment.tier !== 'simple') {
|
|
2353
|
+
combinedContextParts.push(getScalingGuidance(this.lastComplexityAssessment));
|
|
2354
|
+
}
|
|
2355
|
+
}
|
|
2356
|
+
const combinedContext = combinedContextParts.join('\n');
|
|
1860
2357
|
const promptOptions = {
|
|
1861
2358
|
rules: rulesContent + (skillsPrompt ? '\n\n' + skillsPrompt : ''),
|
|
1862
2359
|
tools: toolDescriptions,
|
|
@@ -1996,6 +2493,8 @@ export class ProductionAgent {
|
|
|
1996
2493
|
},
|
|
1997
2494
|
},
|
|
1998
2495
|
});
|
|
2496
|
+
// Pause duration budget during LLM call - network time shouldn't count against agent
|
|
2497
|
+
this.economics?.pauseDuration();
|
|
1999
2498
|
try {
|
|
2000
2499
|
let response;
|
|
2001
2500
|
let actualModel = model;
|
|
@@ -2135,6 +2634,10 @@ export class ProductionAgent {
|
|
|
2135
2634
|
this.observability?.tracer?.endSpan(spanId);
|
|
2136
2635
|
throw error;
|
|
2137
2636
|
}
|
|
2637
|
+
finally {
|
|
2638
|
+
// Resume duration budget after LLM call completes (success or failure)
|
|
2639
|
+
this.economics?.resumeDuration();
|
|
2640
|
+
}
|
|
2138
2641
|
}
|
|
2139
2642
|
/**
|
|
2140
2643
|
* Execute an async callback while excluding wall-clock wait time from duration budgeting.
|
|
@@ -2287,6 +2790,12 @@ export class ProductionAgent {
|
|
|
2287
2790
|
});
|
|
2288
2791
|
// Handle forbidden policy - always block
|
|
2289
2792
|
if (evaluation.policy === 'forbidden') {
|
|
2793
|
+
this.emit({
|
|
2794
|
+
type: 'policy.tool.blocked',
|
|
2795
|
+
tool: toolCall.name,
|
|
2796
|
+
phase: 'enforced',
|
|
2797
|
+
reason: `Forbidden by execution policy: ${evaluation.reason}`,
|
|
2798
|
+
});
|
|
2290
2799
|
throw new Error(`Forbidden by policy: ${evaluation.reason}`);
|
|
2291
2800
|
}
|
|
2292
2801
|
// Handle prompt policy - requires approval
|
|
@@ -2329,6 +2838,21 @@ export class ProductionAgent {
|
|
|
2329
2838
|
const safety = this.safety;
|
|
2330
2839
|
const validation = await this.withPausedDuration(() => safety.validateAndApprove(toolCall, `Executing tool: ${toolCall.name}`, { skipHumanApproval: policyApprovedByUser }));
|
|
2331
2840
|
if (!validation.allowed) {
|
|
2841
|
+
this.emit({
|
|
2842
|
+
type: 'policy.tool.blocked',
|
|
2843
|
+
tool: toolCall.name,
|
|
2844
|
+
phase: 'enforced',
|
|
2845
|
+
reason: validation.reason || 'Blocked by safety manager',
|
|
2846
|
+
});
|
|
2847
|
+
if (toolCall.name === 'bash') {
|
|
2848
|
+
const args = toolCall.arguments;
|
|
2849
|
+
this.emit({
|
|
2850
|
+
type: 'policy.bash.blocked',
|
|
2851
|
+
phase: 'enforced',
|
|
2852
|
+
command: String(args.command || args.cmd || ''),
|
|
2853
|
+
reason: validation.reason || 'Blocked by safety manager',
|
|
2854
|
+
});
|
|
2855
|
+
}
|
|
2332
2856
|
throw new Error(`Tool call blocked: ${validation.reason}`);
|
|
2333
2857
|
}
|
|
2334
2858
|
}
|
|
@@ -2467,6 +2991,8 @@ export class ProductionAgent {
|
|
|
2467
2991
|
this.blackboard.release(filePath, agentId);
|
|
2468
2992
|
}
|
|
2469
2993
|
}
|
|
2994
|
+
// Self-improvement: record success pattern
|
|
2995
|
+
this.selfImprovement?.recordSuccess(toolCall.name, toolCall.arguments, typeof result === 'string' ? result.slice(0, 200) : JSON.stringify(result).slice(0, 200));
|
|
2470
2996
|
this.observability?.tracer?.endSpan(spanId);
|
|
2471
2997
|
return { callId: toolCall.id, result };
|
|
2472
2998
|
}
|
|
@@ -2494,6 +3020,12 @@ export class ProductionAgent {
|
|
|
2494
3020
|
error,
|
|
2495
3021
|
intent: `Execute tool ${toolCall.name}`,
|
|
2496
3022
|
});
|
|
3023
|
+
// Self-improvement: enhance error message with diagnosis for better LLM recovery
|
|
3024
|
+
if (this.selfImprovement) {
|
|
3025
|
+
const enhanced = this.selfImprovement.enhanceErrorMessage(toolCall.name, error.message, toolCall.arguments);
|
|
3026
|
+
this.emit({ type: 'tool.blocked', tool: toolCall.name, reason: enhanced });
|
|
3027
|
+
return { callId: toolCall.id, result: `Error: ${enhanced}`, error: enhanced };
|
|
3028
|
+
}
|
|
2497
3029
|
this.emit({ type: 'tool.blocked', tool: toolCall.name, reason: error.message });
|
|
2498
3030
|
return { callId: toolCall.id, result: `Error: ${error.message}`, error: error.message };
|
|
2499
3031
|
}
|
|
@@ -3381,6 +3913,12 @@ export class ProductionAgent {
|
|
|
3381
3913
|
return null;
|
|
3382
3914
|
return this.economics.getProgress();
|
|
3383
3915
|
}
|
|
3916
|
+
/**
|
|
3917
|
+
* Get actual file paths modified during this agent's session.
|
|
3918
|
+
*/
|
|
3919
|
+
getModifiedFilePaths() {
|
|
3920
|
+
return this.economics?.getModifiedFilePaths() ?? [];
|
|
3921
|
+
}
|
|
3384
3922
|
/**
|
|
3385
3923
|
* Extend the budget limits.
|
|
3386
3924
|
*/
|
|
@@ -3522,6 +4060,19 @@ export class ProductionAgent {
|
|
|
3522
4060
|
}
|
|
3523
4061
|
// Create the checkpoint
|
|
3524
4062
|
const label = `auto-iter-${this.state.iteration}`;
|
|
4063
|
+
// Supplementary: also save to AutoCheckpointManager (file-based)
|
|
4064
|
+
if (this.autoCheckpointManager) {
|
|
4065
|
+
try {
|
|
4066
|
+
this.autoCheckpointManager.save({
|
|
4067
|
+
label,
|
|
4068
|
+
sessionId: this.agentId,
|
|
4069
|
+
iteration: this.state.iteration,
|
|
4070
|
+
});
|
|
4071
|
+
}
|
|
4072
|
+
catch {
|
|
4073
|
+
// Non-critical — don't fail the main checkpoint path
|
|
4074
|
+
}
|
|
4075
|
+
}
|
|
3525
4076
|
return this.createCheckpoint(label);
|
|
3526
4077
|
}
|
|
3527
4078
|
// =========================================================================
|
|
@@ -3675,7 +4226,75 @@ export class ProductionAgent {
|
|
|
3675
4226
|
let workerResultId;
|
|
3676
4227
|
try {
|
|
3677
4228
|
// Filter tools for this agent
|
|
3678
|
-
|
|
4229
|
+
let agentTools = filterToolsForAgent(agentDef, Array.from(this.tools.values()));
|
|
4230
|
+
// Resolve policy profile FIRST so we know which tools the policy allows.
|
|
4231
|
+
// This must happen before the recommendation filter so policy-allowed tools
|
|
4232
|
+
// are preserved through the recommendation pruning step.
|
|
4233
|
+
const inferredTaskType = agentDef.taskType ?? ToolRecommendationEngine.inferTaskType(agentName);
|
|
4234
|
+
const policyResolution = resolvePolicyProfile({
|
|
4235
|
+
policyEngine: this.config.policyEngine,
|
|
4236
|
+
requestedProfile: agentDef.policyProfile,
|
|
4237
|
+
swarmConfig: isSwarmWorker && this.config.swarm && typeof this.config.swarm === 'object'
|
|
4238
|
+
? this.config.swarm
|
|
4239
|
+
: undefined,
|
|
4240
|
+
taskType: inferredTaskType,
|
|
4241
|
+
isSwarmWorker,
|
|
4242
|
+
sandboxConfig: this.config.sandbox && typeof this.config.sandbox === 'object'
|
|
4243
|
+
? this.config.sandbox
|
|
4244
|
+
: undefined,
|
|
4245
|
+
});
|
|
4246
|
+
this.emit({
|
|
4247
|
+
type: 'policy.profile.resolved',
|
|
4248
|
+
profile: policyResolution.profileName,
|
|
4249
|
+
context: isSwarmWorker ? 'swarm' : 'subagent',
|
|
4250
|
+
selectionSource: policyResolution.metadata.selectionSource,
|
|
4251
|
+
usedLegacyMappings: policyResolution.metadata.usedLegacyMappings,
|
|
4252
|
+
legacySources: policyResolution.metadata.legacyMappingSources,
|
|
4253
|
+
});
|
|
4254
|
+
if (policyResolution.metadata.usedLegacyMappings) {
|
|
4255
|
+
this.emit({
|
|
4256
|
+
type: 'policy.legacy.fallback.used',
|
|
4257
|
+
profile: policyResolution.profileName,
|
|
4258
|
+
sources: policyResolution.metadata.legacyMappingSources,
|
|
4259
|
+
warnings: policyResolution.metadata.warnings,
|
|
4260
|
+
});
|
|
4261
|
+
this.observability?.logger?.warn('Policy legacy mappings used', {
|
|
4262
|
+
agent: agentName,
|
|
4263
|
+
profile: policyResolution.profileName,
|
|
4264
|
+
sources: policyResolution.metadata.legacyMappingSources,
|
|
4265
|
+
});
|
|
4266
|
+
}
|
|
4267
|
+
// Apply tool recommendations to improve subagent focus (only for large tool sets)
|
|
4268
|
+
if (this.toolRecommendation && agentTools.length > 15) {
|
|
4269
|
+
const taskType = ToolRecommendationEngine.inferTaskType(agentName);
|
|
4270
|
+
const recommendations = this.toolRecommendation.recommendTools(task, taskType, agentTools.map(t => t.name));
|
|
4271
|
+
if (recommendations.length > 0) {
|
|
4272
|
+
const recommendedNames = new Set(recommendations.map(r => r.toolName));
|
|
4273
|
+
// Always keep spawn tools even if not recommended
|
|
4274
|
+
const alwaysKeep = new Set(['spawn_agent', 'spawn_agents_parallel']);
|
|
4275
|
+
// Also keep tools that the resolved policy profile explicitly allows.
|
|
4276
|
+
// This prevents the recommendation engine from stripping tools that the
|
|
4277
|
+
// security policy says the worker should have.
|
|
4278
|
+
if (policyResolution.profile.allowedTools) {
|
|
4279
|
+
for (const t of policyResolution.profile.allowedTools)
|
|
4280
|
+
alwaysKeep.add(t);
|
|
4281
|
+
}
|
|
4282
|
+
agentTools = agentTools.filter(t => recommendedNames.has(t.name) || alwaysKeep.has(t.name));
|
|
4283
|
+
}
|
|
4284
|
+
}
|
|
4285
|
+
// Enforce unified tool policy at spawn-time so denied tools are never exposed.
|
|
4286
|
+
if (policyResolution.profile.toolAccessMode === 'whitelist' && policyResolution.profile.allowedTools) {
|
|
4287
|
+
const allowed = new Set(policyResolution.profile.allowedTools);
|
|
4288
|
+
agentTools = agentTools.filter(t => allowed.has(t.name));
|
|
4289
|
+
}
|
|
4290
|
+
else if (policyResolution.profile.deniedTools && policyResolution.profile.deniedTools.length > 0) {
|
|
4291
|
+
const denied = new Set(policyResolution.profile.deniedTools);
|
|
4292
|
+
agentTools = agentTools.filter(t => !denied.has(t.name));
|
|
4293
|
+
}
|
|
4294
|
+
// Fail fast if tool filtering resulted in zero tools — the worker can't do anything
|
|
4295
|
+
if (agentTools.length === 0) {
|
|
4296
|
+
throw new Error(`Worker '${agentName}' has zero available tools after filtering. Check toolAccessMode and policy profile '${policyResolution.profileName}'.`);
|
|
4297
|
+
}
|
|
3679
4298
|
// Resolve model - abstract tiers (fast/balanced/quality) should use parent's model
|
|
3680
4299
|
// Only use agentDef.model if it's an actual model ID (contains '/')
|
|
3681
4300
|
const resolvedModel = (agentDef.model && agentDef.model.includes('/'))
|
|
@@ -3698,7 +4317,8 @@ export class ProductionAgent {
|
|
|
3698
4317
|
// Precedence: per-type config > per-type default > global config > hardcoded fallback
|
|
3699
4318
|
const subagentConfig = this.config.subagent;
|
|
3700
4319
|
const hasSubagentConfig = subagentConfig !== false && subagentConfig !== undefined;
|
|
3701
|
-
// Timeout precedence: per-type config
|
|
4320
|
+
// Timeout precedence: agentDef.timeout > per-type config > agent-type default > global config default
|
|
4321
|
+
// agentDef.timeout is set by worker-pool for swarm workers, giving them precise timeout control
|
|
3702
4322
|
const agentTypeTimeout = getSubagentTimeout(agentName);
|
|
3703
4323
|
const rawPerTypeTimeout = hasSubagentConfig
|
|
3704
4324
|
? subagentConfig.timeouts?.[agentName]
|
|
@@ -3708,9 +4328,10 @@ export class ProductionAgent {
|
|
|
3708
4328
|
: undefined;
|
|
3709
4329
|
// Validate: reject negative, NaN, or non-finite timeout values
|
|
3710
4330
|
const isValidTimeout = (v) => v !== undefined && Number.isFinite(v) && v > 0;
|
|
4331
|
+
const agentDefTimeout = isValidTimeout(agentDef.timeout) ? agentDef.timeout : undefined;
|
|
3711
4332
|
const perTypeConfigTimeout = isValidTimeout(rawPerTypeTimeout) ? rawPerTypeTimeout : undefined;
|
|
3712
4333
|
const globalConfigTimeout = isValidTimeout(rawGlobalTimeout) ? rawGlobalTimeout : undefined;
|
|
3713
|
-
const subagentTimeout = perTypeConfigTimeout ?? agentTypeTimeout ?? globalConfigTimeout ?? 300000;
|
|
4334
|
+
const subagentTimeout = agentDefTimeout ?? perTypeConfigTimeout ?? agentTypeTimeout ?? globalConfigTimeout ?? 300000;
|
|
3714
4335
|
// Iteration precedence: per-type config override > agent-type default > global config default
|
|
3715
4336
|
const agentTypeMaxIter = getSubagentMaxIterations(agentName);
|
|
3716
4337
|
const rawPerTypeMaxIter = hasSubagentConfig
|
|
@@ -3767,14 +4388,29 @@ export class ProductionAgent {
|
|
|
3767
4388
|
// BUDGET AWARENESS: Always inject so subagent understands its limits
|
|
3768
4389
|
const subagentBudgetTokens = constraints?.maxTokens ?? SUBAGENT_BUDGET.maxTokens ?? 100000;
|
|
3769
4390
|
const subagentBudgetMinutes = Math.round((SUBAGENT_BUDGET.maxDuration ?? 240000) / 60000);
|
|
3770
|
-
|
|
3771
|
-
|
|
3772
|
-
|
|
3773
|
-
|
|
3774
|
-
|
|
3775
|
-
|
|
3776
|
-
|
|
3777
|
-
|
|
4391
|
+
if (isSwarmWorker) {
|
|
4392
|
+
// V8: Minimal resource awareness for swarm workers — removes budget/time
|
|
4393
|
+
// messaging entirely to prevent cheap models from bail-out anxiety.
|
|
4394
|
+
// The economics system handles budget warnings via system messages when needed.
|
|
4395
|
+
// Wrapup JSON format is ONLY injected when requestWrapup() is called.
|
|
4396
|
+
constraintParts.push(`**Execution Mode:** You are a focused worker agent.\n` +
|
|
4397
|
+
`- Complete your assigned task using tool calls.\n` +
|
|
4398
|
+
`- Your FIRST action must be a tool call (read_file, write_file, edit_file, grep, glob, etc.).\n` +
|
|
4399
|
+
`- To create files use write_file. To modify files use edit_file. Do NOT use bash for file operations.\n` +
|
|
4400
|
+
`- You will receive a system message if you need to wrap up. Until then, work normally.\n` +
|
|
4401
|
+
`- Do NOT produce summaries or reports — produce CODE and FILE CHANGES.`);
|
|
4402
|
+
}
|
|
4403
|
+
else {
|
|
4404
|
+
// Original RESOURCE AWARENESS text for regular subagents
|
|
4405
|
+
constraintParts.push(`**RESOURCE AWARENESS (CRITICAL):**\n` +
|
|
4406
|
+
`- Token budget: ~${(subagentBudgetTokens / 1000).toFixed(0)}k tokens\n` +
|
|
4407
|
+
`- Time limit: ~${subagentBudgetMinutes} minutes\n` +
|
|
4408
|
+
`- You will receive warnings at 70% usage. When warned, WRAP UP immediately.\n` +
|
|
4409
|
+
`- Do not explore indefinitely - be focused and efficient.\n` +
|
|
4410
|
+
`- If approaching limits, summarize findings and return.\n` +
|
|
4411
|
+
`- **STRUCTURED WRAPUP:** When told to wrap up, respond with ONLY this JSON (no tool calls):\n` +
|
|
4412
|
+
` {"findings":[...], "actionsTaken":[...], "failures":[...], "remainingWork":[...], "suggestedNextSteps":[...]}`);
|
|
4413
|
+
}
|
|
3778
4414
|
if (constraints) {
|
|
3779
4415
|
if (constraints.focusAreas && constraints.focusAreas.length > 0) {
|
|
3780
4416
|
constraintParts.push(`**FOCUS AREAS (limit exploration to these paths):**\n${constraints.focusAreas.map(a => ` - ${a}`).join('\n')}`);
|
|
@@ -3790,14 +4426,36 @@ export class ProductionAgent {
|
|
|
3790
4426
|
}
|
|
3791
4427
|
}
|
|
3792
4428
|
const constraintContext = `\n\n**EXECUTION CONSTRAINTS:**\n${constraintParts.join('\n\n')}\n`;
|
|
4429
|
+
// Build delegation-enhanced system prompt
|
|
4430
|
+
let delegationContext = '';
|
|
4431
|
+
if (this.lastComplexityAssessment && this.lastComplexityAssessment.tier !== 'simple') {
|
|
4432
|
+
const spec = createMinimalDelegationSpec(task, agentName);
|
|
4433
|
+
delegationContext = '\n\n' + buildDelegationPrompt(spec);
|
|
4434
|
+
}
|
|
4435
|
+
// Quality self-assessment prompt for subagent
|
|
4436
|
+
const qualityPrompt = '\n\n' + getSubagentQualityPrompt();
|
|
3793
4437
|
// Build subagent system prompt with subagent-specific plan mode addition
|
|
3794
4438
|
const parentMode = this.getMode();
|
|
3795
4439
|
const subagentSystemPrompt = parentMode === 'plan'
|
|
3796
|
-
? `${agentDef.systemPrompt}\n\n${SUBAGENT_PLAN_MODE_ADDITION}${blackboardContext}${constraintContext}`
|
|
3797
|
-
: `${agentDef.systemPrompt}${blackboardContext}${constraintContext}`;
|
|
4440
|
+
? `${agentDef.systemPrompt}\n\n${SUBAGENT_PLAN_MODE_ADDITION}${blackboardContext}${constraintContext}${delegationContext}${qualityPrompt}`
|
|
4441
|
+
: `${agentDef.systemPrompt}${blackboardContext}${constraintContext}${delegationContext}${qualityPrompt}`;
|
|
3798
4442
|
// Allocate budget from pool (or use default) — track allocation ID for release later
|
|
3799
4443
|
const pooledBudget = this.getSubagentBudget(agentName, constraints);
|
|
3800
4444
|
const poolAllocationId = pooledBudget.allocationId;
|
|
4445
|
+
const deniedByProfile = new Set(policyResolution.profile.deniedTools ?? []);
|
|
4446
|
+
const policyToolPolicies = {};
|
|
4447
|
+
for (const toolName of deniedByProfile) {
|
|
4448
|
+
policyToolPolicies[toolName] = {
|
|
4449
|
+
policy: 'forbidden',
|
|
4450
|
+
reason: `Denied by policy profile '${policyResolution.profileName}'`,
|
|
4451
|
+
};
|
|
4452
|
+
}
|
|
4453
|
+
if ((policyResolution.profile.bashMode ?? 'full') === 'disabled') {
|
|
4454
|
+
policyToolPolicies.bash = {
|
|
4455
|
+
policy: 'forbidden',
|
|
4456
|
+
reason: `Bash is disabled by policy profile '${policyResolution.profileName}'`,
|
|
4457
|
+
};
|
|
4458
|
+
}
|
|
3801
4459
|
// Create a sub-agent with the agent's config
|
|
3802
4460
|
// Use SUBAGENT_BUDGET to constrain resource usage (prevents runaway token consumption)
|
|
3803
4461
|
const subAgent = new ProductionAgent({
|
|
@@ -3829,14 +4487,56 @@ export class ProductionAgent {
|
|
|
3829
4487
|
// Lower context window for subagents so percentage-based compaction triggers earlier
|
|
3830
4488
|
maxContextTokens: 80000,
|
|
3831
4489
|
observability: this.config.observability,
|
|
3832
|
-
sandbox:
|
|
4490
|
+
sandbox: (() => {
|
|
4491
|
+
const swarm = this.config.swarm;
|
|
4492
|
+
const extraCmds = swarm && typeof swarm === 'object' && swarm.permissions?.additionalAllowedCommands;
|
|
4493
|
+
const baseSbx = this.config.sandbox;
|
|
4494
|
+
if (baseSbx && typeof baseSbx === 'object') {
|
|
4495
|
+
const sbx = baseSbx;
|
|
4496
|
+
const allowedCommands = extraCmds
|
|
4497
|
+
? [...(sbx.allowedCommands || []), ...extraCmds]
|
|
4498
|
+
: sbx.allowedCommands;
|
|
4499
|
+
return {
|
|
4500
|
+
...sbx,
|
|
4501
|
+
allowedCommands,
|
|
4502
|
+
bashMode: policyResolution.profile.bashMode ?? sbx.bashMode,
|
|
4503
|
+
bashWriteProtection: policyResolution.profile.bashWriteProtection ?? sbx.bashWriteProtection,
|
|
4504
|
+
blockFileCreationViaBash: (policyResolution.profile.bashWriteProtection ?? 'off') === 'block_file_mutation'
|
|
4505
|
+
? true
|
|
4506
|
+
: sbx.blockFileCreationViaBash,
|
|
4507
|
+
};
|
|
4508
|
+
}
|
|
4509
|
+
return baseSbx;
|
|
4510
|
+
})(),
|
|
3833
4511
|
humanInLoop: this.config.humanInLoop,
|
|
3834
4512
|
// Subagents get 'allow' as default policy since they're already
|
|
3835
4513
|
// constrained to their registered tool set. The parent's 'prompt'
|
|
3836
4514
|
// policy can't work without humanInLoop.
|
|
3837
|
-
executionPolicy:
|
|
3838
|
-
|
|
3839
|
-
|
|
4515
|
+
executionPolicy: (() => {
|
|
4516
|
+
const hasPolicyOverrides = Object.keys(policyToolPolicies).length > 0;
|
|
4517
|
+
if (this.config.executionPolicy) {
|
|
4518
|
+
return {
|
|
4519
|
+
...this.config.executionPolicy,
|
|
4520
|
+
defaultPolicy: 'allow',
|
|
4521
|
+
toolPolicies: {
|
|
4522
|
+
...(this.config.executionPolicy.toolPolicies ?? {}),
|
|
4523
|
+
...policyToolPolicies,
|
|
4524
|
+
},
|
|
4525
|
+
};
|
|
4526
|
+
}
|
|
4527
|
+
if (hasPolicyOverrides) {
|
|
4528
|
+
return {
|
|
4529
|
+
enabled: true,
|
|
4530
|
+
defaultPolicy: 'allow',
|
|
4531
|
+
toolPolicies: policyToolPolicies,
|
|
4532
|
+
intentAware: false,
|
|
4533
|
+
};
|
|
4534
|
+
}
|
|
4535
|
+
return this.config.executionPolicy;
|
|
4536
|
+
})(),
|
|
4537
|
+
policyEngine: this.config.policyEngine
|
|
4538
|
+
? { ...this.config.policyEngine, defaultProfile: policyResolution.profileName }
|
|
4539
|
+
: this.config.policyEngine,
|
|
3840
4540
|
threads: false,
|
|
3841
4541
|
// Disable hooks console output in subagents - parent handles event display
|
|
3842
4542
|
hooks: this.config.hooks === false ? false : {
|
|
@@ -3852,7 +4552,10 @@ export class ProductionAgent {
|
|
|
3852
4552
|
fileCache: this.fileCache || undefined,
|
|
3853
4553
|
// CONSTRAINED BUDGET: Use pooled budget when available, falling back to SUBAGENT_BUDGET
|
|
3854
4554
|
// Pooled budget ensures total tree cost stays bounded by parent's budget
|
|
3855
|
-
|
|
4555
|
+
// Merge economicsTuning from agent definition so swarm workers get custom thresholds
|
|
4556
|
+
budget: agentDef.economicsTuning
|
|
4557
|
+
? { ...pooledBudget.budget, tuning: agentDef.economicsTuning }
|
|
4558
|
+
: pooledBudget.budget,
|
|
3856
4559
|
});
|
|
3857
4560
|
// CRITICAL: Subagent inherits parent's mode
|
|
3858
4561
|
// This ensures that if parent is in plan mode:
|
|
@@ -3865,14 +4568,35 @@ export class ProductionAgent {
|
|
|
3865
4568
|
// APPROVAL BATCHING (Improvement P6): Set approval scope for subagents
|
|
3866
4569
|
// Read-only tools are auto-approved; write tools get scoped approval
|
|
3867
4570
|
// This reduces interruptions from ~8 per session to ~1-2
|
|
3868
|
-
|
|
3869
|
-
|
|
3870
|
-
|
|
4571
|
+
// Swarm permissions from config override defaults when present
|
|
4572
|
+
const swarmPerms = this.config.swarm && typeof this.config.swarm === 'object'
|
|
4573
|
+
? this.config.swarm.permissions : undefined;
|
|
4574
|
+
const baseAutoApprove = ['read_file', 'list_files', 'glob', 'grep', 'show_file_history', 'show_session_changes'];
|
|
4575
|
+
const baseScopedApprove = isSwarmWorker
|
|
4576
|
+
? {
|
|
3871
4577
|
write_file: { paths: ['src/', 'tests/', 'tools/'] },
|
|
3872
4578
|
edit_file: { paths: ['src/', 'tests/', 'tools/'] },
|
|
3873
|
-
|
|
3874
|
-
|
|
3875
|
-
|
|
4579
|
+
bash: { paths: ['src/', 'tests/', 'tools/'] },
|
|
4580
|
+
}
|
|
4581
|
+
: {
|
|
4582
|
+
write_file: { paths: ['src/', 'tests/', 'tools/'] },
|
|
4583
|
+
edit_file: { paths: ['src/', 'tests/', 'tools/'] },
|
|
4584
|
+
};
|
|
4585
|
+
const baseRequireApproval = isSwarmWorker ? ['delete_file'] : ['bash', 'delete_file'];
|
|
4586
|
+
const mergedScope = mergeApprovalScopeWithProfile({
|
|
4587
|
+
autoApprove: swarmPerms?.autoApprove
|
|
4588
|
+
? [...new Set([...baseAutoApprove, ...swarmPerms.autoApprove])]
|
|
4589
|
+
: baseAutoApprove,
|
|
4590
|
+
scopedApprove: swarmPerms?.scopedApprove
|
|
4591
|
+
? { ...baseScopedApprove, ...swarmPerms.scopedApprove }
|
|
4592
|
+
: baseScopedApprove,
|
|
4593
|
+
// requireApproval: full replacement (not merge) — user may want to REMOVE
|
|
4594
|
+
// tools like 'bash' to let workers run freely
|
|
4595
|
+
requireApproval: swarmPerms?.requireApproval
|
|
4596
|
+
? swarmPerms.requireApproval
|
|
4597
|
+
: baseRequireApproval,
|
|
4598
|
+
}, policyResolution.profile);
|
|
4599
|
+
subAgent.setApprovalScope(mergedScope);
|
|
3876
4600
|
// Pass parent's iteration count to subagent for accurate budget tracking
|
|
3877
4601
|
// This prevents subagents from consuming excessive iterations when parent already used many
|
|
3878
4602
|
subAgent.setParentIterations(this.getTotalIterations());
|
|
@@ -3893,7 +4617,7 @@ export class ProductionAgent {
|
|
|
3893
4617
|
// 1. Normal operation: progress extends idle timer
|
|
3894
4618
|
// 2. Wrapup phase: 30s before hard kill, wrapup callback fires → forceTextOnly
|
|
3895
4619
|
// 3. Hard kill: race() throws CancellationError after wrapup window
|
|
3896
|
-
const IDLE_TIMEOUT = 120000; //
|
|
4620
|
+
const IDLE_TIMEOUT = agentDef.idleTimeout ?? 120000; // Configurable idle timeout (default: 2 min)
|
|
3897
4621
|
let WRAPUP_WINDOW = 30000;
|
|
3898
4622
|
let IDLE_CHECK_INTERVAL = 5000;
|
|
3899
4623
|
if (this.config.subagent) {
|
|
@@ -3996,6 +4720,8 @@ export class ProductionAgent {
|
|
|
3996
4720
|
: (result.response || result.error || '');
|
|
3997
4721
|
// Parse structured closure report from agent's response (if it produced one)
|
|
3998
4722
|
const structured = parseStructuredClosureReport(result.response || '', 'completed');
|
|
4723
|
+
// Extract real file paths from subagent's economics tracker (before cleanup)
|
|
4724
|
+
const subagentFilePaths = subAgent.getModifiedFilePaths();
|
|
3999
4725
|
const spawnResultFinal = {
|
|
4000
4726
|
success: result.success,
|
|
4001
4727
|
output: finalOutput,
|
|
@@ -4005,7 +4731,27 @@ export class ProductionAgent {
|
|
|
4005
4731
|
toolCalls: result.metrics.toolCalls,
|
|
4006
4732
|
},
|
|
4007
4733
|
structured,
|
|
4734
|
+
filesModified: subagentFilePaths,
|
|
4008
4735
|
};
|
|
4736
|
+
// Save full output to subagent output store (avoids telephone problem)
|
|
4737
|
+
if (this.subagentOutputStore) {
|
|
4738
|
+
const outputEntry = {
|
|
4739
|
+
id: agentId,
|
|
4740
|
+
agentId,
|
|
4741
|
+
agentName,
|
|
4742
|
+
task,
|
|
4743
|
+
fullOutput: finalOutput,
|
|
4744
|
+
structured,
|
|
4745
|
+
filesModified: subagentFilePaths,
|
|
4746
|
+
filesCreated: [],
|
|
4747
|
+
timestamp: new Date(),
|
|
4748
|
+
tokensUsed: result.metrics.totalTokens,
|
|
4749
|
+
durationMs: duration,
|
|
4750
|
+
};
|
|
4751
|
+
const storeId = this.subagentOutputStore.save(outputEntry);
|
|
4752
|
+
// Attach reference so downstream consumers can retrieve full output
|
|
4753
|
+
spawnResultFinal.outputStoreId = storeId;
|
|
4754
|
+
}
|
|
4009
4755
|
if (workerResultId && this.store?.hasWorkerResultsFeature()) {
|
|
4010
4756
|
try {
|
|
4011
4757
|
this.store.completeWorkerResult(workerResultId, {
|
|
@@ -4146,6 +4892,8 @@ export class ProductionAgent {
|
|
|
4146
4892
|
this.pendingPlanManager.appendExplorationFinding(`[${agentName}] ${subPlan.explorationSummary}`);
|
|
4147
4893
|
}
|
|
4148
4894
|
}
|
|
4895
|
+
// Extract real file paths from subagent's economics tracker (before cleanup)
|
|
4896
|
+
const subagentFilePaths = subAgent.getModifiedFilePaths();
|
|
4149
4897
|
// Unsubscribe from subagent events and cleanup gracefully
|
|
4150
4898
|
unsubSubAgent();
|
|
4151
4899
|
try {
|
|
@@ -4213,6 +4961,7 @@ export class ProductionAgent {
|
|
|
4213
4961
|
toolCalls: subagentMetrics.toolCalls,
|
|
4214
4962
|
},
|
|
4215
4963
|
structured,
|
|
4964
|
+
filesModified: subagentFilePaths,
|
|
4216
4965
|
};
|
|
4217
4966
|
}
|
|
4218
4967
|
throw err; // Re-throw non-cancellation errors
|
|
@@ -4310,23 +5059,34 @@ export class ProductionAgent {
|
|
|
4310
5059
|
count: tasks.length,
|
|
4311
5060
|
agents: tasks.map(t => t.agent),
|
|
4312
5061
|
});
|
|
4313
|
-
//
|
|
4314
|
-
//
|
|
4315
|
-
// gets an equal share instead of racing for the full maxPerChild allocation.
|
|
5062
|
+
// Use DynamicBudgetPool for parallel spawns (prevents child starvation,
|
|
5063
|
+
// enables priority-based allocation). Falls back to regular pool for single tasks.
|
|
4316
5064
|
let settled;
|
|
5065
|
+
const originalPool = this.budgetPool;
|
|
5066
|
+
// SubagentSupervisor for unified monitoring of concurrent subagents
|
|
5067
|
+
const supervisor = tasks.length > 1 ? createSubagentSupervisor() : null;
|
|
4317
5068
|
if (this.budgetPool && tasks.length > 1) {
|
|
5069
|
+
// Swap to DynamicBudgetPool for this parallel batch
|
|
4318
5070
|
const poolStats = this.budgetPool.getStats();
|
|
4319
|
-
|
|
4320
|
-
|
|
4321
|
-
// so
|
|
4322
|
-
|
|
4323
|
-
this.budgetPool.setMaxPerChild(equalShare);
|
|
5071
|
+
const dynamicPool = createDynamicBudgetPool(poolStats.tokensRemaining, 0.1);
|
|
5072
|
+
dynamicPool.setExpectedChildren(tasks.length);
|
|
5073
|
+
// Temporarily replace the budget pool so spawnAgent's reserve() uses the dynamic one
|
|
5074
|
+
this.budgetPool = dynamicPool;
|
|
4324
5075
|
try {
|
|
4325
|
-
const promises = tasks.map(({ agent, task }) =>
|
|
5076
|
+
const promises = tasks.map(({ agent, task }) => {
|
|
5077
|
+
const spawnPromise = this.spawnAgent(agent, task);
|
|
5078
|
+
// Register with supervisor for monitoring
|
|
5079
|
+
if (supervisor) {
|
|
5080
|
+
const handle = createSubagentHandle(`parallel-${agent}-${Date.now()}`, agent, task, spawnPromise, {});
|
|
5081
|
+
supervisor.add(handle);
|
|
5082
|
+
}
|
|
5083
|
+
return spawnPromise;
|
|
5084
|
+
});
|
|
4326
5085
|
settled = await Promise.allSettled(promises);
|
|
4327
5086
|
}
|
|
4328
5087
|
finally {
|
|
4329
|
-
this.budgetPool
|
|
5088
|
+
this.budgetPool = originalPool;
|
|
5089
|
+
supervisor?.stop();
|
|
4330
5090
|
}
|
|
4331
5091
|
}
|
|
4332
5092
|
else {
|
|
@@ -5041,8 +5801,19 @@ If the task is a simple question or doesn't need specialized handling, set bestA
|
|
|
5041
5801
|
this.unsubscribers = [];
|
|
5042
5802
|
// Flush trace collector before cleanup
|
|
5043
5803
|
await this.traceCollector?.flush();
|
|
5044
|
-
//
|
|
5045
|
-
|
|
5804
|
+
// Per-agent blackboard cleanup: release only this agent's claims and subscriptions
|
|
5805
|
+
// so parallel siblings don't lose their data. Only root agent clears everything.
|
|
5806
|
+
if (this.blackboard) {
|
|
5807
|
+
if (this.parentIterations > 0 && this.agentId) {
|
|
5808
|
+
// Subagent: release only our claims and subscriptions
|
|
5809
|
+
this.blackboard.releaseAll(this.agentId);
|
|
5810
|
+
this.blackboard.unsubscribeAgent(this.agentId);
|
|
5811
|
+
}
|
|
5812
|
+
else {
|
|
5813
|
+
// Root agent: full clear
|
|
5814
|
+
this.blackboard.clear();
|
|
5815
|
+
}
|
|
5816
|
+
}
|
|
5046
5817
|
// Wait for any pending init before cleanup
|
|
5047
5818
|
if (this.initPromises.length > 0) {
|
|
5048
5819
|
try {
|