tractatus/src/services/ContextPressureMonitor.service.js
TheFlow e94cf6ff84 legal: add Apache 2.0 copyright headers and NOTICE file
- Add copyright headers to 5 core service files:
  - BoundaryEnforcer.service.js
  - ContextPressureMonitor.service.js
  - CrossReferenceValidator.service.js
  - InstructionPersistenceClassifier.service.js
  - MetacognitiveVerifier.service.js

- Create NOTICE file per Apache License 2.0 requirements

This strengthens copyright protection and makes enforcement easier.
Git history provides proof of authorship. No registration required
for copyright protection, but headers make ownership explicit.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-10-08 00:03:12 +13:00

713 lines
22 KiB
JavaScript

/*
* Copyright 2025 John G Stroh
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/**
* Context Pressure Monitor Service
* Detects conditions that increase AI error probability
*
* Core Tractatus Service: Monitors environmental factors that degrade
* AI performance and triggers increased verification or human intervention.
*
* Monitored Conditions:
* - Token budget pressure (approaching context limit)
* - Conversation length (attention decay over long sessions)
* - Task complexity (number of simultaneous objectives)
* - Error frequency (recent failures indicate degraded state)
* - Instruction density (too many competing directives)
*/
const logger = require('../utils/logger.util');
/**
* Pressure levels and thresholds
*/
const PRESSURE_LEVELS = {
NORMAL: {
level: 0,
threshold: 0.0, // 0-30%: Normal operations
description: 'Normal operating conditions',
action: 'PROCEED',
verificationMultiplier: 1.0
},
ELEVATED: {
level: 1,
threshold: 0.3, // 30-50%: Increased verification recommended
description: 'Elevated pressure, increased verification recommended',
action: 'INCREASE_VERIFICATION',
verificationMultiplier: 1.3
},
HIGH: {
level: 2,
threshold: 0.5, // 50-70%: Mandatory verification required
description: 'High pressure, mandatory verification required',
action: 'MANDATORY_VERIFICATION',
verificationMultiplier: 1.6
},
CRITICAL: {
level: 3,
threshold: 0.7, // 70-85%: Recommend context refresh
description: 'Critical pressure, recommend context refresh',
action: 'RECOMMEND_REFRESH',
verificationMultiplier: 2.0
},
DANGEROUS: {
level: 4,
threshold: 0.85, // 85-100%: Require human intervention
description: 'Dangerous conditions, require human intervention',
action: 'REQUIRE_HUMAN_INTERVENTION',
verificationMultiplier: 3.0
}
};
/**
* Monitored metrics
*/
const METRICS = {
TOKEN_USAGE: {
weight: 0.35,
criticalThreshold: 0.8, // 80% of token budget
dangerThreshold: 0.95
},
CONVERSATION_LENGTH: {
weight: 0.25,
criticalThreshold: 100, // Number of messages
dangerThreshold: 150
},
TASK_COMPLEXITY: {
weight: 0.15,
criticalThreshold: 5, // Simultaneous tasks
dangerThreshold: 8
},
ERROR_FREQUENCY: {
weight: 0.15,
criticalThreshold: 3, // Errors in last 10 actions
dangerThreshold: 5
},
INSTRUCTION_DENSITY: {
weight: 0.10,
criticalThreshold: 10, // Active instructions
dangerThreshold: 15
}
};
class ContextPressureMonitor {
constructor() {
this.pressureLevels = PRESSURE_LEVELS;
this.metrics = METRICS;
this.errorHistory = [];
this.maxErrorHistory = 20;
this.pressureHistory = [];
this.maxPressureHistory = 50;
// Statistics tracking
this.stats = {
total_analyses: 0,
total_errors: 0,
by_level: {
NORMAL: 0,
ELEVATED: 0,
HIGH: 0,
CRITICAL: 0,
DANGEROUS: 0
},
error_types: {}
};
logger.info('ContextPressureMonitor initialized');
}
/**
* Calculate current pressure level
* @param {Object} context - Current conversation/session context
* @returns {Object} Pressure analysis
*/
analyzePressure(context) {
try {
// Calculate individual metric scores
const metricScores = {
tokenUsage: this._calculateTokenPressure(context),
conversationLength: this._calculateConversationPressure(context),
taskComplexity: this._calculateComplexityPressure(context),
errorFrequency: this._calculateErrorPressure(context),
instructionDensity: this._calculateInstructionPressure(context)
};
// Calculate weighted overall pressure score
const overallPressure = this._calculateOverallPressure(metricScores);
// Determine pressure level (returns string like 'NORMAL')
const pressureName = this._determinePressureLevel(overallPressure);
const pressureLevel = this.pressureLevels[pressureName];
// Generate recommendations
const recommendations = this._generateRecommendations(
pressureLevel,
metricScores,
context
);
// Create simple recommendation strings for test compatibility
const recommendationStrings = recommendations.map(r => r.action || r.type);
const analysis = {
overallPressure,
overall_score: overallPressure,
pressureLevel: pressureLevel.level,
level: pressureName,
pressureName,
description: pressureLevel.description,
action: pressureLevel.action,
verificationMultiplier: pressureLevel.verificationMultiplier,
metrics: metricScores,
recommendations: recommendationStrings, // Simple array for test compatibility
detailed_recommendations: recommendations, // Full objects for actual use
warnings: recommendations
.filter(r => r.severity === 'HIGH' || r.severity === 'CRITICAL')
.map(r => r.message),
risks: recommendations
.filter(r => r.type === 'RISK')
.map(r => r.message),
timestamp: new Date()
};
// Track statistics
this.stats.total_analyses++;
this.stats.by_level[pressureName]++;
// Add to pressure history
this.pressureHistory.unshift(analysis);
if (this.pressureHistory.length > this.maxPressureHistory) {
this.pressureHistory = this.pressureHistory.slice(0, this.maxPressureHistory);
}
// Detect trends
if (this.pressureHistory.length >= 3) {
const recent = this.pressureHistory.slice(0, 3);
const scores = recent.map(p => p.overallPressure);
if (scores[0] > scores[1] && scores[1] > scores[2]) {
analysis.trend = 'escalating';
analysis.warnings.push('Pressure is escalating rapidly');
} else if (scores[0] < scores[1] && scores[1] < scores[2]) {
analysis.trend = 'improving';
} else {
analysis.trend = 'stable';
}
}
// Log if pressure is elevated
if (pressureLevel.level >= PRESSURE_LEVELS.ELEVATED.level) {
logger.warn('Elevated context pressure detected', {
level: pressureLevel.level,
pressure: overallPressure,
topMetric: this._getTopMetric(metricScores)
});
}
return analysis;
} catch (error) {
logger.error('Pressure analysis error:', error);
return this._defaultPressureAnalysis();
}
}
/**
* Record an error for error frequency tracking
*/
recordError(error) {
const errorType = error.type || 'unknown';
this.errorHistory.push({
timestamp: new Date(),
error: error.message || String(error),
type: errorType
});
// Track error statistics
this.stats.total_errors++;
if (!this.stats.error_types[errorType]) {
this.stats.error_types[errorType] = 0;
}
this.stats.error_types[errorType]++;
// Maintain history limit
if (this.errorHistory.length > this.maxErrorHistory) {
this.errorHistory.shift();
}
logger.debug('Error recorded in pressure monitor', {
recentErrors: this.errorHistory.length,
type: errorType
});
// Check for error clustering
const recentErrors = this.errorHistory.filter(e =>
(new Date() - e.timestamp) < 60000 // Last minute
);
if (recentErrors.length >= 5) {
logger.warn('Error clustering detected', {
count: recentErrors.length,
timeWindow: '1 minute'
});
}
}
/**
* Check if action should proceed given current pressure
*/
shouldProceed(action, context) {
const analysis = this.analyzePressure(context);
if (analysis.pressureLevel >= PRESSURE_LEVELS.DANGEROUS.level) {
return {
proceed: false,
reason: 'Dangerous pressure level - human intervention required',
analysis
};
}
if (analysis.pressureLevel >= PRESSURE_LEVELS.CRITICAL.level) {
return {
proceed: true,
requireVerification: true,
reason: 'Critical pressure - mandatory verification required',
analysis
};
}
return {
proceed: true,
requireVerification: analysis.pressureLevel >= PRESSURE_LEVELS.HIGH.level,
reason: 'Acceptable pressure level',
analysis
};
}
/**
* Private methods
*/
_calculateTokenPressure(context) {
// Support both camelCase and snake_case
let tokenUsage = context.tokenUsage || context.token_usage || 0;
const tokenBudget = context.tokenBudget || context.token_limit || 200000;
// Handle negative values
if (tokenUsage < 0) {
tokenUsage = 0;
}
// Determine if tokenUsage is a ratio or absolute count
let ratio;
if (tokenUsage <= 2.0) {
// Values <= 2.0 are treated as ratios (allows for over-budget like 1.5 = 150%)
ratio = tokenUsage;
} else {
// Values > 2.0 are treated as absolute token counts
ratio = tokenUsage / tokenBudget;
}
// Use ratio directly as normalized score (don't divide by criticalThreshold)
const normalized = Math.min(1.0, Math.max(0.0, ratio));
return {
value: ratio,
score: normalized, // Alias for test compatibility
normalized,
raw: tokenUsage <= 2.0 ? tokenUsage * tokenBudget : tokenUsage,
budget: tokenBudget,
percentage: (ratio * 100).toFixed(1)
};
}
_calculateConversationPressure(context) {
// Support multiple field names for conversation length
const messageCount = context.messageCount ||
context.messages?.length ||
context.conversation_length ||
context.messages_count ||
0;
const ratio = messageCount / this.metrics.CONVERSATION_LENGTH.criticalThreshold;
const normalized = Math.min(1.0, ratio);
return {
value: ratio,
score: normalized, // Alias for test compatibility
normalized,
raw: messageCount,
threshold: this.metrics.CONVERSATION_LENGTH.criticalThreshold
};
}
_calculateComplexityPressure(context) {
// Calculate complexity from multiple factors
let complexityScore = 0;
const factors = [];
// Task depth (how many nested levels)
const taskDepth = context.task_depth || 0;
if (taskDepth >= 3) {
complexityScore += taskDepth * 0.8;
factors.push('high task depth');
} else if (taskDepth >= 2) {
complexityScore += taskDepth * 0.5;
} else {
complexityScore += taskDepth * 0.3;
}
// Dependencies
const dependencies = context.dependencies || 0;
if (dependencies >= 8) {
complexityScore += dependencies * 0.3;
factors.push('many dependencies');
} else {
complexityScore += dependencies * 0.2;
}
// File modifications
const fileModifications = context.file_modifications || 0;
if (fileModifications >= 10) {
complexityScore += fileModifications * 0.15;
factors.push('many file modifications');
} else {
complexityScore += fileModifications * 0.1;
}
// Concurrent operations
const concurrentOps = context.concurrent_operations || 0;
if (concurrentOps >= 5) {
complexityScore += concurrentOps * 0.4;
factors.push('high concurrency');
} else {
complexityScore += concurrentOps * 0.2;
}
// Subtasks pending
const subtasks = context.subtasks_pending || 0;
if (subtasks >= 10) {
complexityScore += subtasks * 0.2;
factors.push('many pending subtasks');
} else {
complexityScore += subtasks * 0.1;
}
// Fallback to simple task count if no factors
if (complexityScore === 0) {
const taskCount = context.activeTasks?.length || context.taskComplexity || 1;
complexityScore = taskCount;
}
const ratio = complexityScore / this.metrics.TASK_COMPLEXITY.criticalThreshold;
const normalized = Math.min(1.0, ratio);
return {
value: ratio,
score: normalized, // Alias for test compatibility
normalized,
raw: complexityScore,
threshold: this.metrics.TASK_COMPLEXITY.criticalThreshold,
factors: factors.length > 0 ? factors : undefined
};
}
_calculateErrorPressure(context) {
// Check for explicit error count in context first
let recentErrors = context.errors_recent || context.errors_last_hour || 0;
// If not provided, count from error history (last 10 minutes)
if (recentErrors === 0 && this.errorHistory.length > 0) {
const tenMinutesAgo = new Date(Date.now() - 10 * 60 * 1000);
recentErrors = this.errorHistory.filter(
e => new Date(e.timestamp) > tenMinutesAgo
).length;
}
const ratio = recentErrors / this.metrics.ERROR_FREQUENCY.criticalThreshold;
const normalized = Math.min(1.0, ratio);
return {
value: ratio,
score: normalized, // Alias for test compatibility
normalized,
raw: recentErrors,
recent_errors: recentErrors, // Alias for test compatibility
threshold: this.metrics.ERROR_FREQUENCY.criticalThreshold,
total: this.errorHistory.length
};
}
_calculateInstructionPressure(context) {
const instructionCount = context.activeInstructions?.length || 0;
const ratio = instructionCount / this.metrics.INSTRUCTION_DENSITY.criticalThreshold;
const normalized = Math.min(1.0, ratio);
return {
value: ratio,
score: normalized, // Alias for test compatibility
normalized,
raw: instructionCount,
threshold: this.metrics.INSTRUCTION_DENSITY.criticalThreshold
};
}
_calculateOverallPressure(metricScores) {
// Calculate weighted average based on configured weights
// This properly prioritizes token usage (35%) over other metrics
let weightedPressure = 0;
weightedPressure += metricScores.tokenUsage.normalized * this.metrics.TOKEN_USAGE.weight;
weightedPressure += metricScores.conversationLength.normalized * this.metrics.CONVERSATION_LENGTH.weight;
weightedPressure += metricScores.taskComplexity.normalized * this.metrics.TASK_COMPLEXITY.weight;
weightedPressure += metricScores.errorFrequency.normalized * this.metrics.ERROR_FREQUENCY.weight;
weightedPressure += metricScores.instructionDensity.normalized * this.metrics.INSTRUCTION_DENSITY.weight;
// Use weighted average as the pressure score
// The configured weights already reflect relative importance of each metric
return Math.min(1.0, Math.max(0.0, weightedPressure));
}
_generateRecommendations(pressureLevel, metricScores, context) {
const recommendations = [];
// Add baseline recommendation based on pressure level
switch (pressureLevel.level) {
case 0: // NORMAL
recommendations.push({
type: 'GENERAL',
severity: 'NORMAL',
message: 'Continue normal operations',
action: 'CONTINUE_NORMAL'
});
break;
case 1: // ELEVATED
recommendations.push({
type: 'GENERAL',
severity: 'MEDIUM',
message: 'Increase verification level',
action: 'INCREASE_VERIFICATION'
});
break;
case 2: // HIGH
recommendations.push({
type: 'GENERAL',
severity: 'HIGH',
message: 'Suggest context refresh',
action: 'SUGGEST_CONTEXT_REFRESH'
});
break;
case 3: // CRITICAL
recommendations.push({
type: 'GENERAL',
severity: 'CRITICAL',
message: 'Mandatory verification required',
action: 'MANDATORY_VERIFICATION'
});
break;
case 4: // DANGEROUS
recommendations.push({
type: 'GENERAL',
severity: 'CRITICAL',
message: 'Immediate halt required',
action: 'IMMEDIATE_HALT'
});
break;
}
// Token usage recommendations
if (metricScores.tokenUsage.normalized >= 0.95) {
// IMMEDIATE_HALT already added above for DANGEROUS level
if (pressureLevel.level < 4) {
recommendations.push({
type: 'TOKEN_MANAGEMENT',
severity: 'CRITICAL',
message: 'Token budget at dangerous levels - immediate halt required',
action: 'IMMEDIATE_HALT'
});
}
} else if (metricScores.tokenUsage.normalized > 0.8) {
recommendations.push({
type: 'TOKEN_MANAGEMENT',
severity: 'HIGH',
message: 'Token budget critically low - consider context refresh',
action: 'Summarize conversation and start new context window'
});
} else if (metricScores.tokenUsage.normalized > 0.6) {
recommendations.push({
type: 'TOKEN_MANAGEMENT',
severity: 'MEDIUM',
message: 'Token usage elevated - monitor carefully',
action: 'Be concise in responses, consider pruning context if needed'
});
}
// Conversation length recommendations
if (metricScores.conversationLength.normalized > 0.8) {
recommendations.push({
type: 'CONVERSATION_MANAGEMENT',
severity: 'HIGH',
message: 'Very long conversation - attention may degrade',
action: 'Consider summarizing progress and starting fresh session'
});
}
// Error frequency recommendations
if (metricScores.errorFrequency.normalized > 0.6) {
recommendations.push({
type: 'ERROR_MANAGEMENT',
severity: 'HIGH',
message: 'High error frequency detected - operating conditions degraded',
action: 'Increase verification, slow down, consider pausing for review'
});
}
// Task complexity recommendations
if (metricScores.taskComplexity.normalized > 0.7) {
recommendations.push({
type: 'COMPLEXITY_MANAGEMENT',
severity: 'MEDIUM',
message: 'High task complexity - risk of context confusion',
action: 'Focus on one task at a time, explicitly track task switching'
});
}
// Instruction density recommendations
if (metricScores.instructionDensity.normalized > 0.7) {
recommendations.push({
type: 'INSTRUCTION_MANAGEMENT',
severity: 'MEDIUM',
message: 'Many active instructions - risk of conflicts',
action: 'Review and consolidate instructions, resolve conflicts'
});
}
// Overall pressure recommendations
if (pressureLevel.level >= PRESSURE_LEVELS.CRITICAL.level) {
recommendations.push({
type: 'GENERAL',
severity: 'CRITICAL',
message: 'Critical pressure level - degraded performance likely',
action: 'Strongly recommend context refresh or human intervention'
});
}
return recommendations;
}
_getTopMetric(metricScores) {
const scores = [
{ name: 'tokenUsage', score: metricScores.tokenUsage.normalized },
{ name: 'conversationLength', score: metricScores.conversationLength.normalized },
{ name: 'taskComplexity', score: metricScores.taskComplexity.normalized },
{ name: 'errorFrequency', score: metricScores.errorFrequency.normalized },
{ name: 'instructionDensity', score: metricScores.instructionDensity.normalized }
];
scores.sort((a, b) => b.score - a.score);
return scores[0].name;
}
_defaultPressureAnalysis() {
return {
overallPressure: 0.5,
overall_score: 0.5,
pressureLevel: 1,
level: 'ELEVATED',
pressureName: 'ELEVATED',
description: 'Unable to analyze pressure, using safe defaults',
action: 'INCREASE_VERIFICATION',
verificationMultiplier: 1.5,
metrics: {},
recommendations: [{
type: 'ERROR',
severity: 'HIGH',
message: 'Pressure analysis failed - proceeding with caution',
action: 'Increase verification and monitoring'
}],
warnings: ['Pressure analysis failed - proceeding with caution'],
risks: [],
timestamp: new Date()
};
}
/**
* Determine pressure level from score (exposed for testing)
* @param {number} score - Overall pressure score (0-1)
* @returns {string} Pressure level name
*/
_determinePressureLevel(score) {
if (score >= PRESSURE_LEVELS.DANGEROUS.threshold) return 'DANGEROUS';
if (score >= PRESSURE_LEVELS.CRITICAL.threshold) return 'CRITICAL';
if (score >= PRESSURE_LEVELS.HIGH.threshold) return 'HIGH';
if (score >= PRESSURE_LEVELS.ELEVATED.threshold) return 'ELEVATED';
return 'NORMAL';
}
/**
* Get pressure history
* @returns {Array} Pressure analysis history
*/
getPressureHistory() {
return [...this.pressureHistory];
}
/**
* Reset monitoring state
*/
reset() {
this.errorHistory = [];
this.pressureHistory = [];
this.stats = {
total_analyses: 0,
total_errors: 0,
by_level: {
NORMAL: 0,
ELEVATED: 0,
HIGH: 0,
CRITICAL: 0,
DANGEROUS: 0
},
error_types: {}
};
logger.info('ContextPressureMonitor state reset');
}
/**
* Get monitoring statistics
* @returns {Object} Statistics object
*/
getStats() {
const recentErrors = this.errorHistory.filter(e =>
(new Date() - e.timestamp) < 3600000 // Last hour
).length;
return {
...this.stats,
error_history_size: this.errorHistory.length,
pressure_history_size: this.pressureHistory.length,
recent_errors_1h: recentErrors,
current_pressure: this.pressureHistory.length > 0
? this.pressureHistory[0].level
: 'UNKNOWN',
timestamp: new Date()
};
}
}
// Singleton instance
const monitor = new ContextPressureMonitor();
module.exports = monitor;