From 8b4741bf880e080c492bc5cc119435898b8c53ae Mon Sep 17 00:00:00 2001 From: LeandroMachadoAveiro Date: Sun, 15 Mar 2026 15:04:28 -0300 Subject: [PATCH 1/2] fix(synapse): dynamic context window from model registry instead of hardcoded 200K The context-tracker was hardcoded to 200K tokens, causing premature bracket escalation on Opus 4.6 (1M context). Now reads active model from core-config.yaml models.registry with graceful fallback to 200K default. Closes #605 Co-Authored-By: Claude Opus 4.6 (1M context) --- .aiox-core/core-config.yaml | 12 +++ .../core/synapse/context/context-tracker.js | 81 +++++++++++++++++-- 2 files changed, 86 insertions(+), 7 deletions(-) diff --git a/.aiox-core/core-config.yaml b/.aiox-core/core-config.yaml index c20f8176f..3b25cf526 100644 --- a/.aiox-core/core-config.yaml +++ b/.aiox-core/core-config.yaml @@ -386,3 +386,15 @@ boundary: - .aiox-core/core/config/template-overrides.js # Memory Intelligence System (Epic MIS) configuration placeholder — MIS-2+ +models: + active: claude-sonnet-4-6 + registry: + claude-opus-4-6: + contextWindow: 1000000 + avgTokensPerPrompt: 1500 + claude-sonnet-4-6: + contextWindow: 200000 + avgTokensPerPrompt: 1500 + claude-haiku-4-5: + contextWindow: 200000 + avgTokensPerPrompt: 1200 diff --git a/.aiox-core/core/synapse/context/context-tracker.js b/.aiox-core/core/synapse/context/context-tracker.js index 3b7899391..0a0913311 100644 --- a/.aiox-core/core/synapse/context/context-tracker.js +++ b/.aiox-core/core/synapse/context/context-tracker.js @@ -5,13 +5,16 @@ * based on estimated token usage. Provides token budgets and layer filtering * per bracket for the SynapseEngine orchestrator. * - * Pure arithmetic module — zero I/O, zero external dependencies. + * Reads model context window from core-config.yaml → models.registry. * * @module core/synapse/context/context-tracker - * @version 1.0.0 + * @version 1.1.0 * @created Story SYN-3 - Context Bracket Tracker */ +const fs = require('fs'); +const path = require('path'); + /** * Bracket definitions with thresholds and token budgets. * @@ -51,12 +54,63 @@ const XML_SAFETY_MULTIPLIER = 1.2; /** * Default configuration values. + * maxContext is the fallback when core-config.yaml is unavailable. */ const DEFAULTS = { avgTokensPerPrompt: 1500, maxContext: 200000, }; +/** Cache for model config (read once per process). */ +let _modelConfigCache = null; + +/** + * Read model configuration from core-config.yaml → models section. + * Returns { contextWindow, avgTokensPerPrompt } for the active model. + * Falls back to DEFAULTS if config is missing or malformed. + * + * @returns {{ maxContext: number, avgTokensPerPrompt: number }} + */ +function getModelConfig() { + if (_modelConfigCache) return _modelConfigCache; + + try { + const yaml = require('js-yaml'); + let configPath = path.join(process.cwd(), '.aios-core', 'core-config.yaml'); + if (!fs.existsSync(configPath)) { + configPath = path.join(process.cwd(), '.aiox-core', 'core-config.yaml'); + } + if (!fs.existsSync(configPath)) { + _modelConfigCache = DEFAULTS; + return _modelConfigCache; + } + + const config = yaml.load(fs.readFileSync(configPath, 'utf8')); + const models = config && config.models; + if (!models || !models.registry || !models.active) { + _modelConfigCache = DEFAULTS; + return _modelConfigCache; + } + + const activeModel = models.registry[models.active]; + if (!activeModel || typeof activeModel.contextWindow !== 'number') { + _modelConfigCache = DEFAULTS; + return _modelConfigCache; + } + + _modelConfigCache = { + maxContext: activeModel.contextWindow, + avgTokensPerPrompt: typeof activeModel.avgTokensPerPrompt === 'number' + ? activeModel.avgTokensPerPrompt + : DEFAULTS.avgTokensPerPrompt, + }; + return _modelConfigCache; + } catch (_err) { + _modelConfigCache = DEFAULTS; + return _modelConfigCache; + } +} + /** * Layer configurations per bracket. * @@ -101,16 +155,20 @@ function calculateBracket(contextPercent) { * Formula: 100 - ((promptCount * avgTokensPerPrompt) / maxContext * 100) * Result is clamped to 0-100 range. * + * Reads maxContext and avgTokensPerPrompt from core-config.yaml → models.registry + * for the active model. Options parameter can override for testing. + * * @param {number} promptCount - Number of prompts in current session - * @param {Object} [options={}] - Configuration options - * @param {number} [options.avgTokensPerPrompt=1500] - Average tokens per prompt - * @param {number} [options.maxContext=200000] - Maximum context window size in tokens + * @param {Object} [options={}] - Configuration options (override config values) + * @param {number} [options.avgTokensPerPrompt] - Average tokens per prompt + * @param {number} [options.maxContext] - Maximum context window size in tokens * @returns {number} Percentage of context remaining (0.0 to 100.0) */ function estimateContextPercent(promptCount, options = {}) { + const modelConfig = getModelConfig(); const { - avgTokensPerPrompt = DEFAULTS.avgTokensPerPrompt, - maxContext = DEFAULTS.maxContext, + avgTokensPerPrompt = modelConfig.avgTokensPerPrompt, + maxContext = modelConfig.maxContext, } = options; if (typeof promptCount !== 'number' || isNaN(promptCount) || promptCount < 0) { @@ -184,6 +242,13 @@ function needsMemoryHints(bracket) { return bracket === 'DEPLETED' || bracket === 'CRITICAL'; } +/** + * Reset the model config cache. Useful for tests or after config changes. + */ +function resetModelConfigCache() { + _modelConfigCache = null; +} + module.exports = { calculateBracket, estimateContextPercent, @@ -191,6 +256,8 @@ module.exports = { getActiveLayers, needsHandoffWarning, needsMemoryHints, + getModelConfig, + resetModelConfigCache, BRACKETS, TOKEN_BUDGETS, DEFAULTS, From 192b3c357415f5bf468912ba4b7d6684ab1dc1ae Mon Sep 17 00:00:00 2001 From: Leandro Aveiro Date: Sun, 15 Mar 2026 16:22:16 -0300 Subject: [PATCH 2/2] fix(synapse): use __dirname-based path resolution and add debug logging Address CodeRabbit review findings: - Replace process.cwd() with __dirname-based resolution for config path lookup - Add optional basePath parameter to getModelConfig() for test injection - Log warning when config loading fails (only in DEBUG/AIOX_DEBUG mode) Co-Authored-By: Claude Opus 4.6 (1M context) --- .aiox-core/core/synapse/context/context-tracker.js | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/.aiox-core/core/synapse/context/context-tracker.js b/.aiox-core/core/synapse/context/context-tracker.js index 0a0913311..ee072ae02 100644 --- a/.aiox-core/core/synapse/context/context-tracker.js +++ b/.aiox-core/core/synapse/context/context-tracker.js @@ -69,16 +69,18 @@ let _modelConfigCache = null; * Returns { contextWindow, avgTokensPerPrompt } for the active model. * Falls back to DEFAULTS if config is missing or malformed. * + * @param {string|null} [basePath=null] - Project root override (defaults to __dirname-based resolution) * @returns {{ maxContext: number, avgTokensPerPrompt: number }} */ -function getModelConfig() { +function getModelConfig(basePath = null) { if (_modelConfigCache) return _modelConfigCache; try { const yaml = require('js-yaml'); - let configPath = path.join(process.cwd(), '.aios-core', 'core-config.yaml'); + const root = basePath || path.resolve(__dirname, '..', '..', '..', '..'); + let configPath = path.join(root, '.aios-core', 'core-config.yaml'); if (!fs.existsSync(configPath)) { - configPath = path.join(process.cwd(), '.aiox-core', 'core-config.yaml'); + configPath = path.join(root, '.aiox-core', 'core-config.yaml'); } if (!fs.existsSync(configPath)) { _modelConfigCache = DEFAULTS; @@ -105,7 +107,10 @@ function getModelConfig() { : DEFAULTS.avgTokensPerPrompt, }; return _modelConfigCache; - } catch (_err) { + } catch (err) { + if (process.env.DEBUG || process.env.AIOX_DEBUG) { + console.warn('[context-tracker] Failed to load model config, using defaults:', err.message); + } _modelConfigCache = DEFAULTS; return _modelConfigCache; }