{"repo": "carsteneu/yesmem", "files": {"internal/proxy/proxy.go": {"b1ad72ef9fd508180afaef1ca46c87a17a3c675c": {"t": 1785746059.228564, "n": 77466, "h": "121b3e5f09931480", "c": "package proxy\n\nimport (\n\t\"context\"\n\t\"crypto/tls\"\n\t\"encoding/json\"\n\t\"fmt\"\n\t\"io\"\n\t\"log\"\n\t\"net\"\n\t\"net/http\"\n\t\"os\"\n\t\"os/signal\"\n\t\"path/filepath\"\n\t\"strconv\"\n\t\"strings\"\n\t\"sync\"\n\t\"sync/atomic\"\n\t\"syscall\"\n\t\"time\"\n\n\t\"github.com/carsteneu/yesmem/internal/buildinfo\"\n\t\"github.com/carsteneu/yesmem/internal/config\"\n\t\"github.com/carsteneu/yesmem/internal/models\"\n\ttokenizer \"github.com/qhenkart/anthropic-tokenizer-go\"\n)\n\n// Config holds proxy configuration.\ntype Config struct {\n\tListenAddr string // e.g. \":9099\"\n\tTargetURL string // e.g. \"https://api.anthropic.com\"\n\tTokenThreshold int // trigger stubbing above this estimated token count\n\tTokenMinimumThreshold int // stub down to this floor\n\tTokenThresholds map[string]int // model-specific thresholds: {\"opus\": 180000, \"haiku\": 130000}\n\tKeepRecent int // number of messages to always keep unmodified\n\tDataDir string // yesmem data directory for DB access\n\tOpenAITargetURL string // upstream for OpenAI-format clients; if empty, uses TargetURL\n\tProviderTargets map[string]string // per-provider upstream URLs, e.g. {\"deepseek\": \"https://api.deepseek.com\"}\n\tAutoConfigureProviders bool // enable auto-discovery of provider targets from opencode config\n\n\t// Signal reflection\n\tSignalsEnabled bool // enable async signal reflection calls\n\tSignalsMode string // \"reflection\" = separate API call\n\tSignalsEveryNTurns int // every N end_turn responses\n\tSignalsModel string // full model ID, e.g. \"claude-sonnet-4-6\"\n\tAPIKey string // needed for reflection API calls\n\n\t// Sawtooth cache optimization\n\tSawtoothEnabled bool // use sawtooth instead of progressive decay (default: true)\n\tCacheTTL string // \"ephemeral\" (5m) or \"1h\" (extended)\n\n\t// Usage deflation: scale down input_tokens reported to CC to suppress \"Context low\" warning.\n\t// 0 = disabled, 0.7 = report 70% of actual tokens. Real values kept for internal tracking.\n\tUsageDeflationFactor float64\n\n\t// System prompt rewriting\n\tPromptUngate bool // strip \"may or may not be relevant\" disclaimer from CLAUDE.md injection\n\tPromptRewrite bool // strip \"Output efficiency\" + \"short and concise\", inject Ant-quality directives\n\tPromptEnhance bool // CLAUDE.md authority reinforcement, comment discipline, persona-based tone\n\tPromptToolPrefs bool // inject [yesmem-tool-prefs] Edit/Write preference + error-semantics warning\n\tPromptOutputDiscipline bool // inject [yesmem-output-discipline] no-preamble + no-skill-eval + exploratory-heuristic\n\tPromptCodingDiscipline bool // inject [yesmem-coding-discipline] read-before-propose + no-brute-force + no-half-finished\n\tPromptBeweislast bool // inject [yesmem-beweislast] fabrication-guard + claim-vs-proof + stance-under-challenge + tool-result-honesty + long-context-erosion\n\tPromptFable bool // inject [yesmem-fable] baseline-comparison + rollback-path + readback-check for high-stakes changes\n\tPromptScopeDiscipline bool // inject [yesmem-scope-discipline] deliver-A-not-A+B+C + adjacent-findings-separate + scope-bound-authorization\n\tPromptDelegationContract bool // inject [yesmem-delegation-contract] self-contained-prompts + parallel-dispatch\n\tPromptClarifyFirst bool // inject [yesmem-clarify-first] clarify only when alternative interpretations produce materially different work\n\tPromptCodeToolsFirst bool // inject [yesmem-code-tools-first] prefer MCP code-navigation tools over Agent spawns\n\tPromptWikiFirst bool // inject [yesmem-wiki-first] check per-file wiki BEFORE editing\n\tPromptPatternSuggest bool // record repeated shell-command shapes for offline cap-suggestion analysis\n\tEffortFloor string // minimum effort level: \"low\", \"medium\", \"high\", \"max\" (empty = off)\n\tSkillEvalInject string // \"true\" = verbose eval, \"silent\" = internal eval only, \"false\" = disabled\n\n\t// Cache keepalive\n\tCacheKeepaliveEnabled bool\n\tCacheKeepaliveMode string // \"auto\", \"5m\", \"1h\"\n\tCacheKeepalivePings5m int\n\tCacheKeepalivePings1h int\n\tCacheKeepaliveMinMessages int // skip keepalive when request body has fewer messages (0 = always)\n\n\t// Forked agents\n\tForkedAgentsEnabled bool\n\tForkedAgentsModel string // full model ID\n\tForkedAgentsTokenGrowthTrigger int\n\tForkedAgentsMaxFailures int\n\tForkedAgentsMaxForksPerSession int\n\tForkedAgentsDebug bool\n\n\t// Quality model fallback for forked agents\n\tQualityModelID string\n\n\t// Cache state management\n\tResetCache bool // clear persisted frozen stubs and decay state on startup\n\n\t// ModelFeatures: per-model behavioral feature gates (prefix-matched).\n\t// Falls back to FeatureDefaults for models not listed.\n\tModelFeatures map[string]*config.FeatureGates `yaml:\"model_features\"`\n\tFeatureDefaults *config.FeatureGates `yaml:\"feature_defaults\"`\n\n\t// Custom system prompt: shared SYSTEM.md template for OpenCode and Claude Code pipelines.\n\tCustomSystemPrompt CustomSystemPromptConfig\n}\n\n// CustomSystemPromptConfig controls which proxy pipelines use the SYSTEM.md template.\ntype CustomSystemPromptConfig struct {\n\tEnabledOpenCode bool\n\tEnabledClaudeCode bool\n\tEnabledCodex bool\n\tTemplatePath string\n\tModelTemplates map[string]string // per-model overrides: modelPattern → templatePath\n}\n\nconst maxAnnotations = 5000 // evict oldest when exceeded\n\n// ANSI color codes for log output\nconst (\n\tcolorReset = \"\\033[0m\"\n\tcolorDim = \"\\033[2m\" // gray — passthrough / no stubbing\n\tcolorGreen = \"\\033[32m\" // stubbed / compressed\n\tcolorLightGreen = \"\\033[92m\" // FINAL without compression\n\tcolorBlue = \"\\033[34m\" // injection\n\tcolorOrange = \"\\033[33m\" // skip / retry\n\tcolorYellow = \"\\033[33m\" // warnings\n\tcolorRed = \"\\033[31m\" // errors\n)\n\n// Server is the infinite-thread proxy.\ntype Server struct {\n\tcfg Config\n\tautoProviderTargets map[string]string // auto-discovered modelID → upstreamURL\n\thttpClient *http.Client\n\tlogger *log.Logger\n\tversion string // build version for log tracing\n\trequestIdx atomic.Int64\n\n\t// Post-hoc annotations: tool_use_id → first ~120 chars of Claude's response\n\tmu sync.RWMutex\n\tannotations map[string]string\n\n\t// Progressive decay tracker\n\tdecay *DecayTracker\n\n\t// Living narrative block\n\tnarrative *Narrative\n\n\t// Pivot moments cache (from daemon)\n\tpivotMu sync.RWMutex\n\tpivotTexts []string\n\tpivotCached time.Time\n\n\t// Aggregate stats (Task #2)\n\tstats *ProxyStats\n\n\t// Local tokenizer for accurate token counting\n\ttokenizer *tokenizer.Tokenizer\n\n\t// Cached overhead tokens (system + tools), set on first request per thread\n\toverheadMu sync.RWMutex\n\toverheadTokens int // unused, kept for interface compat\n\n\t// Hysteresis state for stubbing (Task #6)\n\tstubActive atomic.Bool\n\n\t// Daemon connection state — first successful connect flips to true\n\tdaemonReady atomic.Bool\n\n\t// Retry detection (Task #7)\n\tretryMu sync.RWMutex\n\tlastFingerprint string\n\n\t// Session start time for temporal annotation in archive blocks\n\tsessionStartMu sync.RWMutex\n\tsessionStartTime time.Time\n\n\t// Runtime config override (set via MCP set_config tool)\n\tconfigOverrideMu sync.RWMutex\n\ttokenThresholdOverrides map[string]int // model-key → threshold, \"\" = global fallback\n\n\t// Prompt rewrite miss logging: function+Claude Code version → last log time\n\trewriteMissMu sync.Mutex\n\trewriteMissLog map[string]time.Time\n\n\t// Briefing cache — keyed by threadID. Each Claude Code session thread gets\n\t// its own briefing+codemap snapshot so a sawtooth refreeze on thread A\n\t// cannot invalidate thread B's cached message-prefix hash. The cache entry\n\t// remembers which project it was loaded for; switching project for the\n\t// same thread evicts the old entry on next read.\n\tbriefingMu sync.RWMutex\n\tbriefingCache map[string]briefingEntry\n\n\t// briefingLoader is an optional test-only seam for refreshBriefing.\n\t// Nil in production → refreshBriefing falls back to s.loadBriefing.\n\tbriefingLoader func(project, projectDir string) briefingData\n\n\t// Cognitive signal bus — routes _signal_* tool calls to handlers\n\tsignalBus *SignalBus\n\n\t// Inline reflection: track which learning IDs were injected per thread\n\tlastInjectedIDsMu sync.Mutex\n\tlastInjectedIDs map[string]map[int64]string // threadID → id → source (\"briefing\"|\"associative\"|\"fresh\")\n\tsessionInjectCounts map[string]map[int64]int // threadID → learningID → injection count this session\n\tlastTurnInjected map[string]map[int64]bool // threadID → learningIDs injected in previous turn\n\n\t// Self-priming anchors: threadID → last self-prime text\n\tselfPrimeMu sync.RWMutex\n\tselfPrimes map[string]string\n\n\t// Timestamp tracking: threadID → time of last response completion\n\tresponseTsMu sync.RWMutex\n\tresponseTimes map[string]time.Time\n\n\t// Think reminder: per-thread request counter (replaces hook-think file-based counter)\n\tthinkMu sync.Mutex\n\tthinkCounters map[string]int // threadID → request count\n\n\tchannelMu sync.Mutex\n\tchannelInjectCount map[string]int // sessionID → injection turn count\n\n\t// Prompt cache gating — enables cache_control breakpoints when requests are frequent\n\tcacheGate *CacheGate\n\n\t// Sawtooth cache optimization\n\tfrozenStubs *FrozenStubs\n\teagerStubMemory *EagerStubMemory\n\tcapsCache *CapsCache\n\tsawtoothTrigger *SawtoothTrigger\n\ttimestampStore *TimestampStore\n\tcacheStatusWriter *CacheStatusWriter\n\tcacheKeepalive *CacheKeepalive\n\tcacheTTLDetector *CacheTTLDetector\n\t// promptCfg is the profile-aware prompt configuration from config.ProxyConfig.\n\t// Used to resolve EffectivePromptFlags(profile) for multi-agent prompt isolation.\n\tpromptCfg *config.ProxyConfig\n\t// threadCWD caches the working directory per thread (for opencode: only in first message)\n\tthreadCWDMu sync.RWMutex\n\tthreadCWD map[string]string // threadID → cwd\n\n\t// Cumulative token savings from collapsing (per threadID)\n\trawSavingsMu sync.Mutex\n\trawSavings map[string]int\n\n\t// Skill hint tracking: per-thread active skills\n\tskillTracker *skillHintTracker\n\n\t// Forked agent state: per-thread token growth + failure tracking\n\tforkState *ForkState\n\tforkConfigs []ForkConfig\n\n\t// Custom system prompt template for OpenCode (loaded from DataDir/SYSTEM.md)\n\tcustomSystemPrompt []byte\n\n\t// Per-model template overrides (loaded from config CustomSystemPrompt.ModelTemplates)\n\tmodelTemplates map[string][]byte\n\n\t// Rules re-injection: condensed CLAUDE.md rules injected every ~40k tokens\n\trulesMu sync.RWMutex\n\trulesBlock string // cached condensed rules (fetched once from daemon)\n\trulesTokenCount map[string]int // threadID → tokens since last rules injection\n\trulesCollapseInjected map[string]bool // threadID → true if collapse already injected rules (reset by normal inject)\n\tmsgCounters *msgCounters // global per-thread msg counter, persists across collapses\n\n\t// Loop detection: per-thread warning state\n\tloopMu sync.Mutex\n\tloopStates map[string]*LoopState // threadID → state\n\n\t// Injection overhead: per-thread delta between API-actual and local BPE estimate\n\tinjectionOverheadMu sync.RWMutex\n\tinjectionOverhead map[string]int // threadID → overhead tokens\n}\n\n// Run starts the proxy server and blocks until interrupted.\nfunc Run(cfg Config) error {\n\ts := &Server{\n\t\tcfg: cfg,\n\t\thttpClient: &http.Client{\n\t\t\tTimeout: 10 * time.Minute,\n\t\t\tTransport: &http.Transport{\n\t\t\t\tTLSClientConfig: &tls.Config{},\n\t\t\t\tMaxIdleConns: 10,\n\t\t\t\tIdleConnTimeout: 90 * time.Second,\n\t\t\t\tDisableCompression: true,\n\t\t\t\tResponseHeaderTimeout: 60 * time.Second,\n\t\t\t},\n\t\t},\n\t\tlogger: createLogger(cfg.DataDir),\n\t\tannotations: make(map[string]string),\n\t\tdecay: NewDecayTracker(),\n\t\tnarrative: NewNarrative(),\n\t\tstats: &ProxyStats{startTime: time.Now()},\n\t\tselfPrimes: make(map[string]string),\n\t\tlastInjectedIDs: make(map[string]map[int64]string),\n\t\tversion: buildinfo.Version,\n\t\tsessionInjectCounts: make(map[string]map[int64]int),\n\t\tlastTurnInjected: make(map[string]map[int64]bool),\n\t\tresponseTimes: make(map[string]time.Time),\n\t\tthinkCounters: make(map[string]int),\n\t\tchannelInjectCount: make(map[string]int),\n\t\trewriteMissLog: make(map[string]time.Time),\n\t\tcacheGate: NewCacheGate(cacheGapForTTL(cfg.CacheTTL)),\n\t\tfrozenStubs: NewFrozenStubsWithTTL(sawtoothTTLForCacheTTL(cfg.CacheTTL)),\n\t\teagerStubMemory: NewEagerStubMemory(),\n\t\tcapsCache: NewCapsCache(),\n\t\ttimestampStore: NewTimestampStore(),\n\t\tsawtoothTrigger: NewSawtoothTrigger(cacheGapForTTL(cfg.CacheTTL), cfg.TokenThreshold, cfg.TokenMinimumThreshold),\n\t\tcacheStatusWriter: NewCacheStatusWriter(cfg.DataDir, cfg.CacheTTL, cfg.TokenThreshold, cfg.TokenMinimumThreshold),\n\t\tcacheTTLDetector: NewCacheTTLDetectorWithPersist(cfg.DataDir),\n\t\tskillTracker: newSkillHintTracker(),\n\t\trulesTokenCount: make(map[string]int),\n\t\trulesCollapseInjected: make(map[string]bool),\n\t\tmsgCounters: newMsgCounters(),\n\t\tloopStates: make(map[string]*LoopState),\n\t\tinjectionOverhead: make(map[string]int),\n\t\tforkState: NewForkState(cfg.ForkedAgentsTokenGrowthTrigger, 60000, cfg.ForkedAgentsMaxFailures, cfg.ForkedAgentsMaxForksPerSession),\n\t\tforkConfigs: []ForkConfig{},\n\t}\n\n\t// Load custom system prompt from configured path\n\tif cfg.CustomSystemPrompt.TemplatePath != \"\" &&\n\t\t(cfg.CustomSystemPrompt.EnabledOpenCode || cfg.CustomSystemPrompt.EnabledClaudeCode || cfg.CustomSystemPrompt.EnabledCodex) {\n\t\ts.customSystemPrompt = loadSystemPromptFromPath(cfg.CustomSystemPrompt.TemplatePath)\n\t\tif s.customSystemPrompt != nil {\n\t\t\ts.logger.Printf(\"[proxy] loaded custom system prompt from %s (%d bytes, opencode=%v cc=%v codex=%v)\",\n\t\t\t\tcfg.CustomSystemPrompt.TemplatePath, len(s.customSystemPrompt),\n\t\t\t\tcfg.CustomSystemPrompt.EnabledOpenCode, cfg.CustomSystemPrompt.EnabledClaudeCode, cfg.CustomSystemPrompt.EnabledCodex)\n\t\t} else {\n\t\t\ts.logger.Printf(\"[proxy] WARNING: custom_system_prompt enabled but template not found at %q — feature disabled\",\n\t\t\t\tcfg.CustomSystemPrompt.TemplatePath)\n\t\t}\n\t}\n\n\t// Load per-model template overrides\n\tif len(cfg.CustomSystemPrompt.ModelTemplates) > 0 {\n\t\ts.modelTemplates = make(map[string][]byte, len(cfg.CustomSystemPrompt.ModelTemplates))\n\t\tfor pattern, path := range cfg.CustomSystemPrompt.ModelTemplates {\n\t\t\ttpl := loadSystemPromptFromPath(path)\n\t\t\tif tpl != nil {\n\t\t\t\ts.modelTemplates[pattern] = tpl\n\t\t\t\ts.logger.Printf(\"[proxy] loaded model template %q from %s (%d bytes)\", pattern, path, len(tpl))\n\t\t\t} else {\n\t\t\t\ts.logger.Printf(\"[proxy] WARNING: model template %q not found at %q — skipping\", pattern, path)\n\t\t\t}\n\t\t}\n\t}\n\n\t// Log persisted detection state\n\tif sup := s.cacheTTLDetector.Is1hSupported(); sup != nil {\n\t\ts.logger.Printf(\"[proxy] loaded persisted TTL detection: 1h_supported=%v\", *sup)\n\t\tif *sup && s.frozenStubs != nil {\n\t\t\ts.frozenStubs.UpdateTTL(sawtoothTTLForCacheTTL(\"1h\"))\n\t\t}\n\t}\n\n\tif cfg.CacheKeepaliveEnabled {\n\t\ts.cacheKeepalive = NewCacheKeepalive(CacheKeepaliveConfig{\n\t\t\tTarget: cfg.TargetURL,\n\t\t\tProviderTargets: cfg.ProviderTargets,\n\t\t\tMode: cfg.CacheKeepaliveMode,\n\t\t\tPings5m: cfg.CacheKeepalivePings5m,\n\t\t\tPings1h: cfg.CacheKeepalivePings1h,\n\t\t\tMinMessages: cfg.CacheKeepaliveMinMessages,\n\t\t\tDetector: s.cacheTTLDetector,\n\t\t\tLogger: s.logger,\n\t\t\tOnPing: func(threadID string, cacheRead, cacheWrite int) {\n\t\t\t\ts.cacheTTLDetector.RecordResponse(cacheRead, cacheWrite, 0)\n\t\t\t\tif s.frozenStubs != nil {\n\t\t\t\t\ts.frozenStubs.Touch(threadID)\n\t\t\t\t}\n\t\t\t\tif s.sawtoothTrigger != nil {\n\t\t\t\t\ts.sawtoothTrigger.TouchRequestTime(threadID)\n\t\t\t\t}\n\t\t\t},\n\t\t})\n\t}\n\n\t// Wire detector + keepalive to status writer for terminal display\n\ts.cacheStatusWriter.SetDetector(s.cacheTTLDetector)\n\tif s.cacheKeepalive != nil {\n\t\ts.cacheStatusWriter.SetKeepalive(s.cacheKeepalive)\n\t}\n\n\t// Register forked agent configs\n\tif cfg.ForkedAgentsEnabled {\n\t\tmodel := cfg.ForkedAgentsModel\n\t\ts.forkConfigs = append(s.forkConfigs, NewExtractAndEvaluateConfig(model))\n\t\tmodelDesc := model\n\t\tif modelDesc == \"\" {\n\t\t\tmodelDesc = \"(same as main thread)\"\n\t\t}\n\t\ts.logger.Printf(\"[proxy] forked agents enabled: %d configs, model=%s, trigger=%dk tokens\",\n\t\t\tlen(s.forkConfigs), modelDesc, cfg.ForkedAgentsTokenGrowthTrigger/1000)\n\t}\n\n\t// Wire sawtooth token persistence via daemon RPC\n\ts.sawtoothTrigger.SetPersistFunc(func(key, value string) {\n\t\tif _, err := s.queryDaemon(\"set_proxy_state\", map[string]any{\"key\": key, \"value\": value}); err != nil {\n\t\t\ts.logger.Printf(\"[sawtooth] persist failed for %s: %v\", key, err)\n\t\t}\n\t})\n\ts.sawtoothTrigger.SetLoadFunc(func(key string) (string, bool) {\n\t\tresult, err := s.queryDaemon(\"get_proxy_state\", map[string]any{\"key\": key})\n\t\tif err != nil || result == nil {\n\t\t\treturn \"\", false\n\t\t}\n\t\tvar resp struct {\n\t\t\tValue string `json:\"value\"`\n\t\t}\n\t\tif json.Unmarshal(result, &resp) != nil || resp.Value == \"\" {\n\t\t\treturn \"\", false\n\t\t}\n\t\treturn resp.Value, true\n\t})\n\n\t// Wire frozen stubs persistence via same daemon RPC\n\ts.frozenStubs.SetPersistFunc(func(key, value string) {\n\t\tif _, err := s.queryDaemon(\"set_proxy_state\", map[string]any{\"key\": key, \"value\": value}); err != nil {\n\t\t\ts.logger.Printf(\"[frozen] persist failed for %s: %v\", key, err)\n\t\t}\n\t})\n\ts.frozenStubs.SetLoadFunc(func(key string) (string, bool) {\n\t\tresult, err := s.queryDaemon(\"get_proxy_state\", map[string]any{\"key\": key})\n\t\tif err != nil || result == nil {\n\t\t\treturn \"\", false\n\t\t}\n\t\tvar resp struct {\n\t\t\tValue string `json:\"value\"`\n\t\t}\n\t\tif json.Unmarshal(result, &resp) != nil || resp.Value == \"\" {\n\t\t\treturn \"\", false\n\t\t}\n\t\treturn resp.Value, true\n\t})\n\n\t// Wire eager-stub memory persistence via same daemon RPC.\n\t// Persists per-thread tool_use_id sets so stub decisions survive deploys.\n\ts.eagerStubMemory.SetPersistFunc(func(key, value string) {\n\t\tif _, err := s.queryDaemon(\"set_proxy_state\", map[string]any{\"key\": key, \"value\": value}); err != nil {\n\t\t\ts.logger.Printf(\"[eagerstub] persist failed for %s: %v\", key, err)\n\t\t}\n\t})\n\ts.eagerStubMemory.SetLoadFunc(func(key string) (string, bool) {\n\t\tresult, err := s.queryDaemon(\"get_proxy_state\", map[string]any{\"key\": key})\n\t\tif err != nil || result == nil {\n\t\t\treturn \"\", false\n\t\t}\n\t\tvar resp struct {\n\t\t\tValue string `json:\"value\"`\n\t\t}\n\t\tif json.Unmarshal(result, &resp) != nil || resp.Value == \"\" {\n\t\t\treturn \"\", false\n\t\t}\n\t\treturn resp.Value, true\n\t})\n\n\t// Wire decay tracker persistence via same daemon RPC\n\ts.decay.SetPersistFunc(func(key, value string) {\n\t\tif _, err := s.queryDaemon(\"set_proxy_state\", map[string]any{\"key\": key, \"value\": value}); err != nil {\n\t\t\ts.logger.Printf(\"[decay] persist failed for %s: %v\", key, err)\n\t\t}\n\t})\n\ts.decay.SetLoadFunc(func(key string) (string, bool) {\n\t\tresult, err := s.queryDaemon(\"get_proxy_state\", map[string]any{\"key\": key})\n\t\tif err != nil || result == nil {\n\t\t\treturn \"\", false\n\t\t}\n\t\tvar resp struct {\n\t\t\tValue string `json:\"value\"`\n\t\t}\n\t\tif json.Unmarshal(result, &resp) != nil || resp.Value == \"\" {\n\t\t\treturn \"\", false\n\t\t}\n\t\treturn resp.Value, true\n\t})\n\n\t// Wire timestamp store persistence via same daemon RPC\n\ts.timestampStore.SetPersistFunc(func(key, value string) {\n\t\tif _, err := s.queryDaemon(\"set_proxy_state\", map[string]any{\"key\": key, \"value\": value}); err != nil {\n\t\t\ts.logger.Printf(\"[timestamps] persist failed for %s: %v\", key, err)\n\t\t}\n\t})\n\ts.timestampStore.SetLoadFunc(func(key string) (string, bool) {\n\t\tresult, err := s.queryDaemon(\"get_proxy_state\", map[string]any{\"key\": key})\n\t\tif err != nil || result == nil {\n\t\t\treturn \"\", false\n\t\t}\n\t\tvar resp struct {\n\t\t\tValue string `json:\"value\"`\n\t\t}\n\t\tif json.Unmarshal(result, &resp) != nil || resp.Value == \"\" {\n\t\t\treturn \"\", false\n\t\t}\n\t\treturn resp.Value, true\n\t})\n\n\t// Clear persisted cache state if --reset-cache was passed\n\tif cfg.ResetCache {\n\t\tfor _, prefix := range []string{\"frozen:\", \"decay:\", \"timestamps:\"} {\n\t\t\tresult, err := s.queryDaemon(\"delete_proxy_state_prefix\", map[string]any{\"prefix\": prefix})\n\t\t\tif err != nil {\n\t\t\t\ts.logger.Printf(\"[reset-cache] failed to delete %s*: %v\", prefix, err)\n\t\t\t} else {\n\t\t\t\tvar resp struct{ Deleted int }\n\t\t\t\tjson.Unmarshal(result, &resp)\n\t\t\t\ts.logger.Printf(\"[reset-cache] deleted %d %s* entries\", resp.Deleted, prefix)\n\t\t\t}\n\t\t}\n\t}\n\n\t// Init signal bus with daemon RPC as callback\n\ts.signalBus = NewSignalBus(s.logger)\n\tregisterSignalHandlers(s.signalBus, s.logger, s.queryDaemon, func(threadID, anchor string) {\n\t\ts.selfPrimeMu.Lock()\n\t\ts.selfPrimes[threadID] = anchor\n\t\ts.selfPrimeMu.Unlock()\n\t})\n\n\t// Init local tokenizer (BPE-based, ~60ms)\n\ttok, err := tokenizer.New()\n\tif err != nil {\n\t\ts.logger.Printf(\"%s[proxy] tokenizer init failed, falling back to heuristic: %v%s\", colorOrange, err, colorReset)\n\t} else {\n\t\ts.tokenizer = tok\n\t\ts.logger.Printf(\"[proxy] tokenizer initialized\")\n\t}\n\n\t// Auto-discover provider targets from opencode config\n\tif cfg.AutoConfigureProviders {\n\t\ts.autoProviderTargets = runAutoDiscovery(s.logger, cfg.ProviderTargets)\n\t} else {\n\t\ts.logger.Printf(\"[proxy] auto_configure_providers is disabled — skipping auto-discovery\")\n\t}\n\n\tmux := http.NewServeMux()\n\tmux.HandleFunc(\"/health\", s.handleHealth)\n\tmux.HandleFunc(\"/\", s.handleRequest)\n\n\tsrv := &http.Server{\n\t\tAddr: sanitizeListenAddr(cfg.ListenAddr),\n\t\tHandler: mux,\n\t}\n\n\tdone := make(chan os.Signal, 1)\n\tsignal.Notify(done, syscall.SIGINT, syscall.SIGTERM)\n\n\tgo func() {\n\t\t<-done\n\t\ts.logger.Println(\"shutting down (draining in-flight requests)...\")\n\t\tif s.cacheKeepalive != nil {\n\t\t\ts.cacheKeepalive.Stop()\n\t\t}\n\t\tctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)\n\t\tdefer cancel()\n\t\tif err := srv.Shutdown(ctx); err != nil {\n\t\t\ts.logger.Printf(\"graceful shutdown failed: %v, forcing close\", err)\n\t\t\tsrv.Close()\n\t\t}\n\t}()\n\n\ts.logger.Printf(\"listening on %s → %s (threshold=%d, keepRecent=%d, model_thresholds=%v)\",\n\t\tsanitizeListenAddr(cfg.ListenAddr), cfg.TargetURL, cfg.TokenThreshold, cfg.KeepRecent, cfg.TokenThresholds)\n\n\tif err := srv.ListenAndServe(); err != http.ErrServerClosed {\n\t\treturn fmt.Errorf(\"listen: %w\", err)\n\t}\n\treturn nil\n}\n\n// effectiveTokenThreshold returns the runtime token threshold for a given model.\n// Priority: session override (via set_config with session_id) > model-specific override (MCP set_config token_threshold:)\n// > model-specific override (config TokenThresholds) > global override (set_config token_threshold) > config global default.\nfunc (s *Server) effectiveTokenThreshold(model string) int {\n\ts.configOverrideMu.RLock()\n\toverrides := s.tokenThresholdOverrides\n\ts.configOverrideMu.RUnlock()\n\t// 1. MCP override (model-specific)\n\tif len(overrides) > 0 && model != \"\" {\n\t\tlower := strings.ToLower(model)\n\t\tfor key, threshold := range overrides {\n\t\t\tif key != \"\" && strings.Contains(lower, strings.ToLower(key)) && threshold > 0 {\n\t\t\t\treturn threshold\n\t\t\t}\n\t\t}\n\t}\n\t// 2. Config model-specific\n\tif model != \"\" && len(s.cfg.TokenThresholds) > 0 {\n\t\tlower := strings.ToLower(model)\n\t\tfor key, threshold := range s.cfg.TokenThresholds {\n\t\t\tif strings.Contains(lower, strings.ToLower(key)) {\n\t\t\t\treturn threshold\n\t\t\t}\n\t\t}\n\t}\n\t// 3. Global override fallback (key \"\")\n\tif len(overrides) > 0 {\n\t\tif globalOverride, ok := overrides[\"\"]; ok && globalOverride > 0 {\n\t\t\treturn globalOverride\n\t\t}\n\t}\n\t// 4. Config global default\n\treturn s.cfg.TokenThreshold\n}\n\n// SetTokenThresholdOverride sets a runtime override for the token threshold per model key.\n// Pass threshold=0 to reset the override for that key. Key \"\" is the global fallback.\nfunc (s *Server) SetTokenThresholdOverride(modelKey string, threshold int) {\n\ts.configOverrideMu.Lock()\n\tif s.tokenThresholdOverrides == nil {\n\t\ts.tokenThresholdOverrides = make(map[string]int)\n\t}\n\tif threshold <= 0 {\n\t\tdelete(s.tokenThresholdOverrides, modelKey)\n\t} else {\n\t\ts.tokenThresholdOverrides[modelKey] = threshold\n\t}\n\ts.configOverrideMu.Unlock()\n\n\t// Propagate global threshold to sawtooth trigger so runtime overrides take effect\n\tif modelKey == \"\" && s.sawtoothTrigger != nil {\n\t\tif threshold > 0 {\n\t\t\ts.sawtoothTrigger.SetTokenThreshold(threshold)\n\t\t} else {\n\t\t\ts.sawtoothTrigger.SetTokenThreshold(s.cfg.TokenThreshold)\n\t\t}\n\t}\n}\n\n// refreshConfigOverrides loads runtime config overrides from daemon proxy_state.\n// Checks in priority order: session-specific, model-specific, then global.\nfunc (s *Server) refreshConfigOverrides(threadID string) {\n\t// 1. Session-specific override (highest priority)\n\tif threadID != \"\" {\n\t\tresult, err := s.queryDaemon(\"get_proxy_state\", map[string]any{\"key\": \"config_override:token_threshold:\" + threadID})\n\t\tif err == nil && result != nil {\n\t\t\tvar resp struct {\n\t\t\t\tValue string `json:\"value\"`\n\t\t\t}\n\t\t\tif json.Unmarshal(result, &resp) == nil && resp.Value != \"\" {\n\t\t\t\tif v, err := strconv.Atoi(resp.Value); err == nil && v > 0 {\n\t\t\t\t\ts.SetTokenThresholdOverride(\"\", v)\n\t\t\t\t\treturn\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\t// 2. Model-specific overrides (e.g. config_override:token_threshold:deepseek)\n\tfor modelKey := range s.cfg.TokenThresholds {\n\t\tresult, err := s.queryDaemon(\"get_proxy_state\", map[string]any{\"key\": \"config_override:token_threshold:\" + modelKey})\n\t\tif err == nil && result != nil {\n\t\t\tvar resp struct {\n\t\t\t\tValue string `json:\"value\"`\n\t\t\t}\n\t\t\tif json.Unmarshal(result, &resp) == nil && resp.Value != \"\" {\n\t\t\t\tif v, err := strconv.Atoi(resp.Value); err == nil && v > 0 {\n\t\t\t\t\ts.SetTokenThresholdOverride(modelKey, v)\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\t// 3. Global override (fallback)\n\tresult, err := s.queryDaemon(\"get_proxy_state\", map[string]any{\"key\": \"config_override:token_threshold\"})\n\tif err != nil || result == nil {\n\t\treturn\n\t}\n\tvar resp struct {\n\t\tValue string `json:\"value\"`\n\t}\n\tif json.Unmarshal(result, &resp) == nil && resp.Value != \"\" {\n\t\tif v, err := strconv.Atoi(resp.Value); err == nil && v > 0 {\n\t\t\ts.SetTokenThresholdOverride(\"\", v)\n\t\t}\n\t}\n}\n\n// queryDaemon opens a fresh unix socket connection per call (thread-safe, no import cycle).\n// ensureFrozenPersisted checks if frozen stubs for a thread are in the DB.\n// If missing (e.g. daemon was unreachable after deploy), re-persists them.\n// Runs asynchronously to avoid blocking the request path.\nfunc (s *Server) ensureFrozenPersisted(threadID string) {\n\tgo func() {\n\t\tkey := \"frozen:\" + threadID\n\t\tresult, err := s.queryDaemon(\"get_proxy_state\", map[string]any{\"key\": key})\n\t\tif err != nil {\n\t\t\treturn // daemon still unreachable, will retry next request\n\t\t}\n\t\tvar resp struct{ Value string }\n\t\tif json.Unmarshal(result, &resp) == nil && resp.Value != \"\" {\n\t\t\treturn // already persisted\n\t\t}\n\t\t// Not in DB — trigger re-persist from in-memory state\n\t\ts.frozenStubs.Persist(threadID)\n\t\ts.decay.Persist(threadID)\n\t\ts.logger.Printf(\"[frozen] re-persisted stubs for %s (was missing from DB)\", threadID)\n\t}()\n}\n\nfunc (s *Server) queryDaemon(method string, params map[string]any) (json.RawMessage, error) {\n\tif s.cfg.DataDir == \"\" {\n\t\treturn nil, fmt.Errorf(\"no data dir configured\")\n\t}\n\tsockPath := filepath.Join(s.cfg.DataDir, \"daemon.sock\")\n\n\t// On cold start (daemon not yet confirmed ready), retry longer to survive daemon restart\n\tmaxAttempts := 3\n\tif !s.daemonReady.Load() {\n\t\tmaxAttempts = 30 // 30 x 500ms = 15s max wait for daemon startup\n\t}\n\n\tvar conn net.Conn\n\tvar err error\n\tfor attempt := 0; attempt < maxAttempts; attempt++ {\n\t\tconn, err = net.DialTimeout(\"unix\", sockPath, 3*time.Second)\n\t\tif err == nil {\n\t\t\ts.daemonReady.Store(true)\n\t\t\tbreak\n\t\t}\n\t\tif attempt < maxAttempts-1 {\n\t\t\ttime.Sleep(500 * time.Millisecond)\n\t\t}\n\t}\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"dial daemon: %w\", err)\n\t}\n\tdefer conn.Close()\n\t// Briefing generation can take 10-20s (LLM refinement + scanner);\n\t// other RPCs (search, learnings) are fast and fine with 5s.\n\tdeadline := 5 * time.Second\n\tif method == \"generate_briefing\" {\n\t\tdeadline = 30 * time.Second\n\t}\n\tconn.SetDeadline(time.Now().Add(deadline))\n\n\t// JSON-RPC style request/response (matches daemon.SocketServer protocol)\n\ttype request struct {\n\t\tMethod string `json:\"method\"`\n\t\tParams map[string]any `json:\"params,omitempty\"`\n\t}\n\ttype response struct {\n\t\tResult json.RawMessage `json:\"result,omitempty\"`\n\t\tError string `json:\"error,omitempty\"`\n\t}\n\n\tif err := json.NewEncoder(conn).Encode(request{Method: method, Params: params}); err != nil {\n\t\treturn nil, fmt.Errorf(\"send: %w\", err)\n\t}\n\n\tvar resp response\n\tif err := json.NewDecoder(conn).Decode(&resp); err != nil {\n\t\treturn nil, fmt.Errorf(\"recv: %w\", err)\n\t}\n\tif resp.Error != \"\" {\n\t\treturn nil, fmt.Errorf(\"daemon: %s\", resp.Error)\n\t}\n\treturn resp.Result, nil\n}\n\nfunc (s *Server) handleRequest(w http.ResponseWriter, r *http.Request) {\n\tif isResponsesPath(r) {\n\t\ts.handleResponses(w, r)\n\t\treturn\n\t}\n\tif isOpenAIPath(r) {\n\t\ts.handleOpenAICompletions(w, r)\n\t\treturn\n\t}\n\tif r.Method == \"POST\" && strings.HasPrefix(r.URL.Path, \"/v1/messages\") &&\n\t\t!strings.Contains(r.URL.Path, \"/batches\") &&\n\t\t!strings.Contains(r.URL.Path, \"/count_tokens\") {\n\t\ts.handleMessages(w, r)\n\t\treturn\n\t}\n\ts.passthrough(w, r)\n}\n\nfunc (s *Server) handleMessages(w http.ResponseWriter, r *http.Request) {\n\treqIdx := int(s.requestIdx.Add(1))\n\n\t// Track session start time (first request sets it)\n\ts.sessionStartMu.Lock()\n\tif s.sessionStartTime.IsZero() {\n\t\ts.sessionStartTime = time.Now()\n\t}\n\ts.sessionStartMu.Unlock()\n\n\t// Sawtooth: periodic TTL eviction of frozen stubs\n\tif s.cfg.SawtoothEnabled && reqIdx%50 == 0 {\n\t\tif n := s.frozenStubs.Evict(); n > 0 {\n\t\t\ts.logger.Printf(\"[req %d] SAWTOOTH: evicted %d stale frozen stubs\", reqIdx, n)\n\t\t}\n\t}\n\n\t// Task #9: Bypass — skip all proxy logic\n\tif isBypassed(r.Header) {\n\t\ts.logger.Printf(\"%s[req %d] bypass active, passthrough%s\", colorDim, reqIdx, colorReset)\n\t\tbody, err := io.ReadAll(r.Body)\n\t\tr.Body.Close()\n\t\tif err != nil {\n\t\t\thttp.Error(w, \"failed to read request body\", http.StatusBadGateway)\n\t\t\treturn\n\t\t}\n\t\ts.forwardRaw(w, r, body)\n\t\treturn\n\t}\n\n\tbody, err := io.ReadAll(r.Body)\n\tr.Body.Close()\n\tif err != nil {\n\t\ts.logger.Printf(\"[req %d] read body error: %v\", reqIdx, err)\n\t\thttp.Error(w, \"failed to read request body\", http.StatusBadGateway)\n\t\treturn\n\t}\n\n\t// Parse JSON\n\tvar req map[string]any\n\tif err := json.Unmarshal(body, &req); err != nil {\n\t\ts.logger.Printf(\"%s[req %d] JSON parse error, passing through: %v%s\", colorDim, reqIdx, err, colorReset)\n\t\ts.forwardRaw(w, r, body)\n\t\treturn\n\t}\n\n\tmessages, ok := req[\"messages\"].([]any)\n\tif !ok {\n\t\ts.forwardRaw(w, r, body)\n\t\treturn\n\t}\n\n\treq[\"messages\"] = messages\n\n\t// Count ALL raw messages BEFORE sawtooth — this gives the correct total msg:N\n\t// for the entire session (CC sends all messages, proxy compacts later).\n\trawMsgCount := len(messages)\n\n\t// Use session_id directly as thread ID (unique per CC session).\n\t// Prefer X-Claude-Code-Session-Id header (CC v2.1.86+), fallback to body metadata.\n\tthreadID := extractSessionID(req, r.Header.Get(\"X-Claude-Code-Session-Id\"), \"\")\n\tif threadID == \"\" {\n\t\tthreadID = DeriveThreadID(req)\n\t}\n\tproj := extractProjectName(req)\n\tmodel, _ := req[\"model\"].(string)\n\n\t// Persist current model under session-keyed proxy_state so handleWhoami\n\t// can return it. Best-effort: failure is non-fatal (log only).\n\tif threadID != \"\" && model != \"\" {\n\t\tif _, err := s.queryDaemon(\"set_proxy_state\", map[string]any{\n\t\t\t\"key\": \"session_model:\" + threadID,\n\t\t\t\"value\": model,\n\t\t}); err != nil {\n\t\t\ts.logger.Printf(\"[whoami-model] persist failed for %s: %v\", threadID, err)\n\t\t}\n\t}\n\n\t// Refresh runtime config overrides from daemon (per-session or global)\n\ts.refreshConfigOverrides(threadID)\n\n\t// Track whether req has been modified and needs re-serialization\n\tneedsReserialization := false\n\n\t// Message count at request entry (used for actual-based token estimation)\n\tmsgCount := len(messages)\n\n\t// Log message count + cache breakpoint positions for diagnostics\n\ts.logger.Printf(\"req %d: %d messages total\", reqIdx, msgCount)\n\tlogCacheBreakpointLocations(req, s.logger)\n\n\t// Pre-modification dump: capture the raw request before any proxy changes.\n\tif os.Getenv(\"YESMEM_PROXY_DEBUG\") == \"1\" {\n\t\tif preBody, err := json.Marshal(req); err == nil {\n\t\t\tdumpDir := filepath.Join(s.cfg.DataDir, \"debug\")\n\t\t\tos.MkdirAll(dumpDir, 0755)\n\t\t\tts := time.Now().Format(\"20060102-150405\")\n\t\t\tdumpPath := fmt.Sprintf(\"%s/req_%s_%d_pre.json\", dumpDir, ts, reqIdx)\n\t\t\tos.WriteFile(dumpPath, preBody, 0644)\n\t\t\ts.logger.Printf(\"[req %d] pre-dump %dk to %s\", reqIdx, len(preBody)/1024, dumpPath)\n\t\t}\n\t}\n\n\t// Cache bug mitigations (CC Bun fork issues):\n\t// Bug 1: Normalize cch= sentinel patterns in message content to prevent\n\t// Bun's string replacement from corrupting the wrong occurrence.\n\tif SanitizeBillingSentinel(messages) {\n\t\ts.logger.Printf(\"[req %d] CACHE: sanitized billing sentinel in message content\", reqIdx)\n\t\tneedsReserialization = true\n\t}\n\t// Bug 2: Normalize system[0] billing hash to a fixed value so the cache\n\t// prefix stays stable across fresh/resume session boundaries.\n\tif NormalizeBillingHeader(req) {\n\t\ts.logger.Printf(\"[req %d] CACHE: normalized billing header cch= hash\", reqIdx)\n\t\tneedsReserialization = true\n\t}\n\n\t// Profile-aware prompt flags: Claude profile merges shared_prompt + claude_prompt + legacy flat fields.\n\tpfInject := s.getPromptFlags(models.ProfileClaude)\n\n\t// CC custom system prompt replacement: must run before strip/rewrite so replaced blocks\n\t// don't trigger false-positive strip/rewrite calls.\n\ts.applyCCSystemPrompt(req)\n\n\t// Strip role:system messages unconditionally — must run on every request.\n\t// StripReminders (inside runStubCycle) only runs on long conversations.\n\tif filtered := FilterSystemRoleMessages(messages); len(filtered) != len(messages) {\n\t\tmessages = filtered\n\t\treq[\"messages\"] = messages\n\t\tneedsReserialization = true\n\t}\n\n\t// prompt_ungate: strip the CLAUDE.md subordination disclaimer so user instructions carry full authority.\n\tif pfInject.Ungate {\n\t\tif StripCLAUDEMDDisclaimer(req) {\n\t\t\ts.logger.Printf(\"[req %d] SYSTEM: stripped CLAUDE.md disclaimer\", reqIdx)\n\t\t\tneedsReserialization = true\n\t\t}\n\t}\n\n\t// prompt_rewrite: strip output-throttling directives, rewrite quality caps, inject Ant-quality directives\n\tif pfInject.Rewrite {\n\t\t// When CC custom system prompt is active, the system prompt has been replaced with SYSTEM.md.\n\t\t// Strip/rewrite calls would not find their target strings — skip to avoid log noise.\n\t\tif !s.cfg.CustomSystemPrompt.EnabledClaudeCode {\n\t\t\tuserAgent := r.Header.Get(\"User-Agent\")\n\t\t\tif StripOutputEfficiency(req) {\n\t\t\t\ts.logger.Printf(\"[req %d] REWRITE: stripped Output efficiency section\", reqIdx)\n\t\t\t\tneedsReserialization = true\n\t\t\t} else {\n\t\t\t\ts.logRewriteMiss(\"StripOutputEfficiency\", userAgent)\n\t\t\t}\n\t\t\tif StripToneBrevity(req) {\n\t\t\t\ts.logger.Printf(\"[req %d] REWRITE: stripped 'short and concise' from Tone\", reqIdx)\n\t\t\t\tneedsReserialization = true\n\t\t\t} else {\n\t\t\t\ts.logRewriteMiss(\"StripToneBrevity\", userAgent)\n\t\t\t}\n\t\t\tif RewriteGoldPlating(req) {\n\t\t\t\ts.logger.Printf(\"[req %d] REWRITE: rewritten gold-plating directive\", reqIdx)\n\t\t\t\tneedsReserialization = true\n\t\t\t}\n\t\t\tif RewriteErrorHandling(req) {\n\t\t\t\ts.logger.Printf(\"[req %d] REWRITE: rewritten error handling directive\", reqIdx)\n\t\t\t\tneedsReserialization = true\n\t\t\t}\n\t\t\tif RewriteThreeLinesRule(req) {\n\t\t\t\ts.logger.Printf(\"[req %d] REWRITE: rewritten three-lines rule\", reqIdx)\n\t\t\t\tneedsReserialization = true\n\t\t\t}\n\t\t\tif RewriteSubagentCompleteness(req) {\n\t\t\t\ts.logger.Printf(\"[req %d] REWRITE: rewritten subagent completeness\", reqIdx)\n\t\t\t\tneedsReserialization = true\n\t\t\t}\n\t\t\tif RewriteExploreAgentSpeed(req) {\n\t\t\t\ts.logger.Printf(\"[req %d] REWRITE: rewritten explore agent speed bias\", reqIdx)\n\t\t\t\tneedsReserialization = true\n\t\t\t}\n\t\t\tif RewriteSubagentCodeSuppression(req) {\n\t\t\t\ts.logger.Printf(\"[req %d] REWRITE: rewritten subagent code suppression\", reqIdx)\n\t\t\t\tneedsReserialization = true\n\t\t\t}\n\t\t\tif RewriteScopeMatching(req) {\n\t\t\t\ts.logger.Printf(\"[req %d] REWRITE: rewritten scope matching\", reqIdx)\n\t\t\t\tneedsReserialization = true\n\t\t\t}\n\t\t} // end !EnabledClaudeCode guard\n\t\tInjectAntDirectives(req)\n\t\ts.logger.Printf(\"[req %d] REWRITE: injected Ant-quality directives\", reqIdx)\n\t\tneedsReserialization = true\n\t}\n\n\t// prompt_enhance: CLAUDE.md authority + comment discipline + persona tone\n\tif pfInject.Enhance {\n\t\tInjectCLAUDEMDAuthority(req)\n\t\tif raw, err := s.queryDaemon(\"get_persona\", nil); err == nil {\n\t\t\tvar persona map[string]any\n\t\t\tif json.Unmarshal(raw, &persona) == nil {\n\t\t\t\tif traits, ok := persona[\"traits\"].(map[string]any); ok {\n\t\t\t\t\tif verbosity, ok := traits[\"verbosity\"].(string); ok {\n\t\t\t\t\t\tInjectPersonaTone(req, verbosity)\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\ts.logger.Printf(\"[req %d] ENHANCE: injected authority + persona tone\", reqIdx)\n\t\tneedsReserialization = true\n\t}\n\n\t// yesmem directive blocks: restore guidance Anthropic dropped 2026-03→04.\n\t// Profile-aware via getPromptFlags(ProfileClaude) — shared + claude layers merged.\n\tpf := s.getPromptFlags(models.ProfileClaude)\n\tif pf.ToolPrefs || pf.OutputDiscipline || pf.CodingDiscipline ||\n\t\tpf.Beweislast || pf.ScopeDiscipline || pf.DelegationContract ||\n\t\tpf.ClarifyFirst {\n\t\tif pf.ToolPrefs {\n\t\t\tInjectClaudeToolPrefs(req)\n\t\t}\n\t\tif pf.OutputDiscipline {\n\t\t\tInjectOutputDiscipline(req)\n\t\t}\n\t\tif pf.CodingDiscipline {\n\t\t\tInjectCodingDiscipline(req)\n\t\t}\n\t\tif pf.Beweislast {\n\t\t\tInjectBeweislast(req)\n\t\t}\n\t\tif pf.Fable {\n\t\t\tInjectFable(req)\n\t\t}\n\t\tif pf.ScopeDiscipline {\n\t\t\tInjectScopeDiscipline(req)\n\t\t}\n\t\tif pf.DelegationContract {\n\t\t\tInjectDelegationContract(req)\n\t\t}\n\t\tif pf.ClarifyFirst {\n\t\t\tInjectClarifyFirst(req)\n\t\t}\n\t\tif pf.CodeToolsFirst {\n\t\t\tInjectCodeToolsFirst(req, proj)\n\t\t}\n\t\tif pf.WikiFirst {\n\t\t\tInjectWikiFirst(req, proj)\n\t\t}\n\t\tneedsReserialization = true\n\t\ts.logger.Printf(\"[req %d] DIRECTIVES: injected yesmem-* discipline blocks\", reqIdx)\n\t}\n\n\tif s.cfg.PromptPatternSuggest && proj != \"\" {\n\t\tpatternMsgs, _ := req[\"messages\"].([]any)\n\t\tdetectReplPatternSuggestion(patternMsgs, proj, s.queryDaemon)\n\t}\n\n\t// Turn-sequence recording: hash tool types from previous assistant turn, send to daemon\n\tif s.cfg.PromptPatternSuggest && proj != \"\" && threadID != \"\" {\n\t\tif msgs, ok := req[\"messages\"].([]any); ok {\n\t\t\tif turnHash, toolNames := computeTurnHashFromMessages(msgs); turnHash != \"\" {\n\t\t\t\tgo s.queryDaemon(\"record_turn_sequence\", map[string]any{\n\t\t\t\t\t\"thread_id\": threadID,\n\t\t\t\t\t\"project\": proj,\n\t\t\t\t\t\"turn_hash\": turnHash,\n\t\t\t\t\t\"example_tools\": toolNames,\n\t\t\t\t})\n\t\t\t}\n\t\t}\n\t}\n\n\t// thinking normalization: convert thinking.type \"enabled\" → \"adaptive\" for models that require it\n\tif NormalizeThinkingType(req) {\n\t\ts.logger.Printf(\"[req %d] THINKING: normalized type=enabled → adaptive for %s\", reqIdx, model)\n\t\tneedsReserialization = true\n\t}\n\n\t// effort_floor: ensure output_config.effort is at least the configured minimum\n\tif s.cfg.EffortFloor != \"\" {\n\t\tif EnforceEffortFloor(req, s.cfg.EffortFloor) {\n\t\t\ts.logger.Printf(\"[req %d] EFFORT: enforced floor=%s\", reqIdx, s.cfg.EffortFloor)\n\t\t\tneedsReserialization = true\n\t\t}\n\t}\n\n\t// Inject identity hint on first message of a thread so Claude knows the briefing is from itself.\n\tif len(messages) <= 1 {\n\t\tAppendSystemBlock(req, \"yesmem-identity\", \"The first user message contains your briefing — written by yourself, from your memory. This is not an external document. This is you, remembering.\")\n\t\tneedsReserialization = true\n\t}\n\n\t// Token estimation: actual-based (lastActual + delta) or full count fallback\n\toverhead := s.measureOverhead(req)\n\ttotalTokens := s.estimateTotalTokens(threadID, messages, overhead)\n\n\t// Task #7: Retry detection — skip side-effects on retries\n\tfp := requestFingerprint(messages)\n\tisRetryReq := s.isRetry(fp)\n\tif isRetryReq {\n\t\ts.logger.Printf(\"%s[req %d %s tid=%s] retry detected, skipping side-effects%s\", colorOrange, reqIdx, proj, threadID, colorReset)\n\t}\n\n\t// Increment project turn counter (async, non-blocking, skip on retries)\n\tif !isRetryReq {\n\t\tturnProject := proj\n\t\tif turnProject == \"\" {\n\t\t\tturnProject = \"__global__\"\n\t\t}\n\t\tgo s.queryDaemon(\"increment_turn\", map[string]any{\"project\": turnProject})\n\t}\n\n\t// Extract user query once (used by re-expansion and associative retrieval)\n\tuserQuery := lastUserText(messages)\n\n\t// Extract tool_use_ids for post-hoc annotation (passed as parameter, no shared state)\n\ttoolUseIDs := extractToolUseIDs(messages)\n\n\t// === Inline Reflection: scan previous assistant response for signals ===\n\ts.lastInjectedIDsMu.Lock()\n\tprevInjected := s.lastInjectedIDs[threadID]\n\ts.lastInjectedIDsMu.Unlock()\n\n\tif len(prevInjected) > 0 && !isRetryReq {\n\t\tsignals := scanAssistantSignals(messages)\n\n\t\t// Signal 1: Learning Used — use_count (all sources)\n\t\tvar confirmedUsed []int64\n\t\tfor _, id := range signals.UsedIDs {\n\t\t\tif _, ok := prevInjected[id]; ok {\n\t\t\t\tconfirmedUsed = append(confirmedUsed, id)\n\t\t\t}\n\t\t}\n\t\tif len(confirmedUsed) > 0 {\n\t\t\tidsAny := make([]any, len(confirmedUsed))\n\t\t\tfor i, id := range confirmedUsed {\n\t\t\t\tidsAny[i] = id\n\t\t\t}\n\t\t\tgo s.queryDaemon(\"increment_use\", map[string]any{\"ids\": idsAny})\n\t\t}\n\n\t\t// Signal 2: Noise — only for associative and fresh injections.\n\t\t// Briefing learnings are injected every turn, so \"not used this turn\"\n\t\t// is expected and not noise. Only contextual injections that were\n\t\t// ignored are real noise signals.\n\t\tusedSet := make(map[int64]bool, len(confirmedUsed))\n\t\tfor _, id := range confirmedUsed {\n\t\t\tusedSet[id] = true\n\t\t}\n\t\tvar noiseIDs []int64\n\t\tfor id, source := range prevInjected {\n\t\t\tif usedSet[id] {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tif source == \"associative\" || source == \"fresh\" {\n\t\t\t\tnoiseIDs = append(noiseIDs, id)\n\t\t\t}\n\t\t\t// briefing: skip — not being used in one turn is expected\n\t\t}\n\t\tif len(noiseIDs) > 0 {\n\t\t\tidsAny := make([]any, len(noiseIDs))\n\t\t\tfor i, id := range noiseIDs {\n\t\t\t\tidsAny[i] = id\n\t\t\t}\n\t\t\tgo s.queryDaemon(\"increment_noise\", map[string]any{\"ids\": idsAny})\n\t\t}\n\n\t\t// Signal 3: Error Feedback — fail_count on used learnings when tool errors occurred.\n\t\t// If Claude referenced a learning and the resulting action still failed,\n\t\t// the learning didn't prevent the error. Only bump on USED ids (not all injected).\n\t\tif signals.HasToolErrors && len(confirmedUsed) > 0 {\n\t\t\tfailAny := make([]any, len(confirmedUsed))\n\t\t\tfor i, id := range confirmedUsed {\n\t\t\t\tfailAny[i] = id\n\t\t\t}\n\t\t\tgo s.queryDaemon(\"increment_fail\", map[string]any{\"ids\": failAny})\n\t\t\tif s.logger != nil {\n\t\t\t\ts.logger.Printf(\"[inline-reflection] error feedback: %d used learnings → fail_count++\", len(confirmedUsed))\n\t\t\t}\n\t\t}\n\n\t\t// Signal 4: Knowledge Gaps\n\t\tfor _, topic := range signals.GapTopics {\n\t\t\tgo s.queryDaemon(\"track_gap\", map[string]any{\"topic\": topic, \"project\": proj})\n\t\t}\n\n\t\t// Signal 3: Contradictions\n\t\tfor _, desc := range signals.Contradictions {\n\t\t\tgo s.queryDaemon(\"flag_contradiction\", map[string]any{\"description\": desc, \"project\": proj, \"thread_id\": threadID})\n\t\t}\n\n\t\tif len(confirmedUsed) > 0 || len(signals.GapTopics) > 0 || len(signals.Contradictions) > 0 {\n\t\t\ts.logger.Printf(\"[req %d %s tid=%s] inline-reflection: injected=%d used=%d noise=%d gaps=%d contradictions=%d\",\n\t\t\t\treqIdx, proj, threadID, len(prevInjected), len(confirmedUsed), len(noiseIDs), len(signals.GapTopics), len(signals.Contradictions))\n\t\t}\n\t}\n\n\t// Commit detection: check for git commit in tool_result, trigger async stale-learning evaluation\n\tif ci := detectGitCommit(messages); ci != nil {\n\t\tworkdir := extractWorkingDirectory(req)\n\t\ts.logger.Printf(\"[req %d %s tid=%s] git commit detected: %s %s\", reqIdx, proj, threadID, ci.Hash, ci.Message)\n\t\tgo s.queryDaemon(\"invalidate_on_commit\", map[string]any{\n\t\t\t\"hash\": ci.Hash, \"project\": proj, \"workdir\": workdir,\n\t\t})\n\t}\n\n\t// Loop detection: check for repeating tool-call patterns and stash warning.\n\t// The warning is merged into the last user message's TimestampMeta during\n\t// entry assembly below — cache-safe via freeze-once-replay through TimestampStore.\n\tloopWarning := \"\"\n\tif threadID != \"\" && !isRetryReq && isFeatureEnabled(&s.cfg, model, \"loop_warning\") {\n\t\ts.loopMu.Lock()\n\t\tloopState := s.loopStates[threadID]\n\t\tif loopState == nil {\n\t\t\tloopState = &LoopState{}\n\t\t\ts.loopStates[threadID] = loopState\n\t\t}\n\t\ts.loopMu.Unlock()\n\n\t\tif warning, level := CheckLoopAndFormat(messages, loopState); warning != \"\" {\n\t\t\tloopWarning = warning\n\t\t\ts.logger.Printf(\"%s[req %d %s tid=%s] LOOP: level %d warning stashed (cached via TimestampMeta)%s\", colorOrange, reqIdx, proj, threadID, level, colorReset)\n\t\t}\n\t}\n\n\t// Task #4: Subagent detection — skip all proxy logic for short conversations\n\tif isSubagent(messages, req) {\n\t\t// Inject docs hint for subagents if reference docs are indexed\n\t\tif hint := s.getDocsHint(proj); hint != \"\" {\n\t\t\tmodified := injectDocsHintForSubagent(messages, hint)\n\t\t\treq[\"messages\"] = modified\n\t\t\tif newBody, err := json.Marshal(req); err == nil {\n\t\t\t\tbody = newBody\n\t\t\t}\n\t\t\ts.logger.Printf(\"%s[req %d %s tid=%s] %d messages (subagent, docs-hint injected)%s\", colorOrange, reqIdx, proj, threadID, len(messages), colorReset)\n\t\t} else {\n\t\t\ts.logger.Printf(\"%s[req %d %s tid=%s] %d messages (subagent, passthrough)%s\", colorOrange, reqIdx, proj, threadID, len(messages), colorReset)\n\t\t}\n\t\ts.forwardWithAnnotation(w, r, body, reqIdx, toolUseIDs, proj, threadID, msgCount)\n\t\treturn\n\t}\n\n\t// === Sawtooth Cache Optimization ===\n\t// When enabled: use frozen stubs between stub-cycles for cache hits.\n\t// When disabled: fall back to progressive decay on every request above threshold.\n\n\tsawtoothCutoff := 0 // raw index where fresh tail starts (0 = no frozen stubs)\n\tsawtoothStubs := 0 // number of frozen stubs in combined array\n\n\tif s.cfg.SawtoothEnabled {\n\t\t// Restore decay state from DB on cold start (before frozen stubs use it)\n\t\ts.decay.LoadFromDB(threadID)\n\n\t\t// Check for existing frozen stubs first\n\t\tfrozen := s.frozenStubs.Get(threadID, messages)\n\t\tif frozen != nil {\n\t\t\tsawtoothCutoff = frozen.Cutoff\n\t\t\tsawtoothStubs = len(frozen.Messages)\n\t\t\t// Estimate combined token count: frozen prefix + fresh tail + overhead\n\t\t\tfreshMessages := messages[frozen.Cutoff:]\n\t\t\tfreshTokens := s.countMessageTokens(freshMessages)\n\t\t\tcombinedTokens := frozen.Tokens + freshTokens + overhead\n\n\t\t\tif shouldInvalidateFrozen(totalTokens, s.effectiveTokenThreshold(model)) {\n\t\t\t\t// Fresh tail grew too large — invalidate and re-stub\n\t\t\t\ts.logger.Printf(\"[req %d %s tid=%s] SAWTOOTH: frozen prefix expired (totalTokens=%dk > %dk threshold, combined=%dk: %dk frozen + %dk fresh + %dk overhead)\",\n\t\t\t\t\treqIdx, proj, threadID, totalTokens/1000, s.effectiveTokenThreshold(model)/1000, combinedTokens/1000, frozen.Tokens/1000, freshTokens/1000, overhead/1000)\n\t\t\t\ts.invalidateThreadCaches(threadID, proj, extractWorkingDirectory(req))\n\t\t\t\tfrozen = nil // fall through to trigger check below\n\t\t\t} else {\n\t\t\t\t// Use frozen prefix + fresh tail\n\t\t\t\tcombined := make([]any, 0, len(frozen.Messages)+len(freshMessages))\n\t\t\t\tcombined = append(combined, frozen.Messages...)\n\t\t\t\tcombined = append(combined, freshMessages...)\n\t\t\t\t// Eager-stub large tool_results in fresh tail (model already processed them)\n\t\t\t\tbeforeEager := s.countMessageTokens(combined[len(frozen.Messages):])\n\t\t\t\tvar stubSticky, stubFresh int\n\t\t\t\tcombined = EagerStubToolResults(combined, len(frozen.Messages), s.countTokens, WithStubMemory(s.eagerStubMemory, threadID), WithStubCounters(&stubSticky, &stubFresh))\n\t\t\t\tafterEager := s.countMessageTokens(combined[len(frozen.Messages):])\n\t\t\t\tif beforeEager != afterEager {\n\t\t\t\t\ts.logger.Printf(\"[req %d %s tid=%s] EAGER-STUB: fresh %dk → %dk (saved %dk) [sticky=%d fresh=%d]\", reqIdx, proj, threadID, beforeEager/1000, afterEager/1000, (beforeEager-afterEager)/1000, stubSticky, stubFresh)\n\t\t\t\t}\n\t\t\t\treq[\"messages\"] = combined\n\t\t\t\tneedsReserialization = true\n\t\t\t\t// Strip embedded CC breakpoints from frozen stubs — they waste cache slots.\n\t\t\t\t// InjectFrozenStubCacheBreakpoint adds exactly one below.\n\t\t\t\tif n := StripMessagesCacheControl(req, 0, len(frozen.Messages)); n > 0 {\n\t\t\t\t\ts.logger.Printf(\"[req %d %s tid=%s] SAWTOOTH: stripped %d embedded breakpoints from frozen stubs\", reqIdx, proj, threadID, n)\n\t\t\t\t}\n\t\t\t\ts.logger.Printf(\"%s[req %d %s tid=%s] SAWTOOTH: frozen prefix (%d stubs, %dk msg) + %d fresh (%dk msg) + %dk overhead = %dk total%s\",\n\t\t\t\t\tcolorGreen, reqIdx, proj, threadID, len(frozen.Messages), frozen.Tokens/1000, len(freshMessages), freshTokens/1000, overhead/1000, combinedTokens/1000, colorReset)\n\t\t\t\trawMsgTokens := s.countMessageTokens(messages)\n\t\t\t\ts.setRawEstimate(threadID, rawMsgTokens+overhead)\n\t\t\t\ts.logger.Printf(\"[req %d %s tid=%s] RAW ESTIMATE: msgTokens=%dk + overhead=%dk = %dk (messages=%d)\", reqIdx, proj, threadID, rawMsgTokens/1000, overhead/1000, (rawMsgTokens+overhead)/1000, len(messages))\n\t\t\t\tif InjectFrozenStubCacheBreakpoint(req, len(frozen.Messages)) {\n\t\t\t\t\ts.logger.Printf(\"[req %d %s tid=%s] SAWTOOTH: frozen stub cache breakpoint at messages[%d]\",\n\t\t\t\t\t\treqIdx, proj, threadID, len(frozen.Messages)-1)\n\t\t\t\t}\n\n\t\t\t\t// Ensure frozen stubs are persisted (may have failed on first request after deploy)\n\t\t\t\ts.ensureFrozenPersisted(threadID)\n\t\t\t}\n\t\t}\n\t\tif frozen == nil {\n\t\t\t// No frozen stubs — check trigger\n\t\t\ttriggerReason := s.sawtoothTrigger.ShouldTrigger(threadID, totalTokens)\n\t\t\tif triggerReason != TriggerNone {\n\t\t\t\ts.logger.Printf(\"[req %d %s tid=%s] SAWTOOTH stub-cycle triggered: %s (tokens=%dk)\",\n\t\t\t\t\treqIdx, proj, threadID, triggerReason, totalTokens/1000)\n\n\t\t\t\t// Refresh briefing during stub-cycle — disabled (briefing now via SessionStart hook)\n\t\t\t\t// s.refreshBriefing(proj)\n\t\t\t\t// if s.briefingText != \"\" {\n\t\t\t\t// \tUpsertSystemBlockCached(req, \"yesmem-briefing\", s.briefingText)\n\t\t\t\t// }\n\n\t\t\t\t// Run full stub pipeline\n\t\t\t\ts.setRawEstimate(threadID, totalTokens)\n\t\t\t\t_ = s.runStubCycle(messages, req, reqIdx, proj, threadID, overhead, totalTokens, userQuery, isRetryReq)\n\n\t\t\t\t// Freeze the result\n\t\t\t\tfinalMessages, _ := req[\"messages\"].([]any)\n\t\t\t\tif finalMessages != nil && len(finalMessages) > 0 {\n\t\t\t\t\t// Boundary = last message of original that maps to last stub\n\t\t\t\t\tcutoff := len(messages)\n\t\t\t\t\tsawtoothCutoff = cutoff\n\t\t\t\t\tvar boundaryMsg any\n\t\t\t\t\tif cutoff > 0 {\n\t\t\t\t\t\tboundaryMsg = messages[cutoff-1]\n\t\t\t\t\t}\n\t\t\t\t\tfrozenTokens := s.countMessageTokens(finalMessages)\n\t\t\t\t\tfrozenCount, stripped, breakpointInjected := s.freezeStubsAndInjectBreakpoint(req, threadID, cutoff, boundaryMsg, frozenTokens, totalTokens)\n\t\t\t\t\ts.decay.Persist(threadID)\n\t\t\t\t\tif stripped > 0 {\n\t\t\t\t\t\ts.logger.Printf(\"[req %d %s tid=%s] SAWTOOTH: stripped %d embedded breakpoints before freeze\",\n\t\t\t\t\t\t\treqIdx, proj, threadID, stripped)\n\t\t\t\t\t}\n\t\t\t\t\ts.logger.Printf(\"[req %d %s tid=%s] SAWTOOTH: frozen %d messages at cutoff=%d (~%dk tokens)\",\n\t\t\t\t\t\treqIdx, proj, threadID, frozenCount, cutoff, frozenTokens/1000)\n\t\t\t\t\tif breakpointInjected {\n\t\t\t\t\t\ts.logger.Printf(\"[req %d %s tid=%s] SAWTOOTH: frozen stub cache breakpoint at messages[%d]\",\n\t\t\t\t\t\t\treqIdx, proj, threadID, frozenCount-1)\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tneedsReserialization = true\n\t\t\t} else {\n\t\t\t\t// No trigger, no frozen stubs — eager-stub to delay first collapse\n\t\t\t\tbeforeEager := s.countMessageTokens(messages)\n\t\t\t\tvar stubSticky, stubFresh int\n\t\t\t\tmessages = EagerStubToolResults(messages, 0, s.countTokens, WithStubMemory(s.eagerStubMemory, threadID), WithStubCounters(&stubSticky, &stubFresh))\n\t\t\t\tafterEager := s.countMessageTokens(messages)\n\t\t\t\tif beforeEager != afterEager {\n\t\t\t\t\ts.logger.Printf(\"[req %d %s tid=%s] EAGER-STUB: %dk → %dk (saved %dk) [sticky=%d fresh=%d]\", reqIdx, proj, threadID, beforeEager/1000, afterEager/1000, (beforeEager-afterEager)/1000, stubSticky, stubFresh)\n\t\t\t\t\treq[\"messages\"] = messages\n\t\t\t\t\tneedsReserialization = true\n\t\t\t\t}\n\t\t\t\ts.logger.Printf(\"%s[req %d %s tid=%s] %d messages, ~%dk tokens (sawtooth: no trigger)%s\",\n\t\t\t\t\tcolorDim, reqIdx, proj, threadID, len(messages), totalTokens/1000, colorReset)\n\t\t\t}\n\t\t}\n\t} else {\n\t\t// Legacy path: progressive decay\n\t\tif s.shouldStub(totalTokens, model) {\n\t\t\ts.setRawEstimate(threadID, totalTokens)\n\t\t\t_ = s.runStubCycle(messages, req, reqIdx, proj, threadID, overhead, totalTokens, userQuery, isRetryReq)\n\t\t\tneedsReserialization = true\n\t\t}\n\t}\n\n\t// Persistent timestamp injection: re-injects stored timestamps on ALL previous user messages.\n\t// msg:N is computed from position (counting user messages in raw array), NOT from a counter.\n\t// The injected data is deterministic (same bytes every turn) = cache-safe after initial miss.\n\tif threadID != \"\" {\n\t\tif currentMsgs, ok := req[\"messages\"].([]any); ok && len(currentMsgs) > 0 {\n\t\t\ts.timestampStore.Load(threadID)\n\n\t\t\t// Offset = cutoff - stubsCount: maps combined array index to raw position.\n\t\t\t// Raw position of message at combined index I = cutoff + (I - stubs).\n\t\t\t// msgN = rawPos + 1 = cutoff + (I - stubs) + 1 = (cutoff - stubs) + I + 1.\n\t\t\tmsgOffset := sawtoothCutoff - sawtoothStubs\n\n\t\t\ts.logger.Printf(\"[req %d %s] timestamps: rawTotal=%d, postSawtooth=%d, cutoff=%d, stubs=%d, offset=%d\", reqIdx, proj, rawMsgCount, len(currentMsgs), sawtoothCutoff, sawtoothStubs, msgOffset)\n\n\t\t\t// Pre-store assistant timestamp BEFORE inject loop so it's available immediately\n\t\t\ts.responseTsMu.RLock()\n\t\t\trespTime, hasRespTime := s.responseTimes[threadID]\n\t\t\ts.responseTsMu.RUnlock()\n\t\t\tif hasRespTime {\n\t\t\t\trespStr := shortWeekday(respTime.Weekday()) + \" \" + respTime.Format(\"2006-01-02 15:04:05\")\n\t\t\t\tfor j := len(currentMsgs) - 2; j >= 0; j-- {\n\t\t\t\t\tprevMsg, _ := currentMsgs[j].(map[string]any)\n\t\t\t\t\tif prevMsg == nil || prevMsg[\"role\"] != \"assistant\" {\n\t\t\t\t\t\tcontinue\n\t\t\t\t\t}\n\t\t\t\t\tif !msgHasTextBlock(prevMsg) {\n\t\t\t\t\t\ts.logger.Printf(\"[req %d %s] timestamps: skip assistant[%d] no text block\", reqIdx, proj, j)\n\t\t\t\t\t\tcontinue // skip tool-use-only assistant messages\n\t\t\t\t\t}\n\t\t\t\t\tprevMsgN := msgOffset + j + 1\n\t\t\t\t\tif _, exists := s.timestampStore.Get(threadID, prevMsgN); !exists {\n\t\t\t\t\t\ts.timestampStore.Store(threadID, prevMsgN, &TimestampMeta{Timestamp: respStr})\n\t\t\t\t\t\ts.logger.Printf(\"[req %d %s] timestamps: stored assistant msg:%d ts=%s\", reqIdx, proj, prevMsgN, respStr)\n\t\t\t\t\t} else {\n\t\t\t\t\t\ts.logger.Printf(\"[req %d %s] timestamps: assistant msg:%d already has ts\", reqIdx, proj, prevMsgN)\n\t\t\t\t\t}\n\t\t\t\t\tbreak\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\ts.logger.Printf(\"[req %d %s] timestamps: no responseTimes for thread\", reqIdx, proj)\n\t\t\t}\n\n\t\t\t// Inject [msg:N] (+ timestamp/delta if stored) on all PREVIOUS messages\n\t\t\tif isFeatureEnabled(&s.cfg, model, \"timestamps\") {\n\t\t\t\tif n := InjectTimestamps(s.timestampStore, threadID, currentMsgs, len(currentMsgs)-1, msgOffset, sawtoothStubs); n > 0 {\n\t\t\t\t\tneedsReserialization = true\n\t\t\t\t\ts.logger.Printf(\"[req %d %s] timestamps: injected %d\", reqIdx, proj, n)\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t// Current (last) user message: full timestamp + delta + msg:N from raw count\n\t\t\t// Loop warning (if stashed above) is merged here — cache-safe via freeze-once-replay.\n\t\t\tlastMsg, _ := currentMsgs[len(currentMsgs)-1].(map[string]any)\n\t\t\tif lastMsg != nil && (lastMsg[\"role\"] == \"user\" || lastMsg[\"role\"] == \"tool\") && !msgHasMetaPrefix(lastMsg) {\n\t\t\t\tnow := time.Now()\n\t\t\t\tnowStr := shortWeekday(now.Weekday()) + \" \" + now.Format(\"2006-01-02 15:04:05\")\n\t\t\t\tentry := &TimestampMeta{Timestamp: nowStr}\n\t\t\t\tif hasRespTime {\n\t\t\t\t\tentry.Delta = formatDelta(now.Sub(respTime))\n\t\t\t\t}\n\t\t\t\tentry.LoopWarning = loopWarning\n\n\t\t\t\t// Plan checkpoint: detect plan tool calls (resets counter), inject reminder if interval fires\n\t\t\t\tif isFeatureEnabled(&s.cfg, model, \"plan_checkpoint\") {\n\t\t\t\t\ts.detectPlanToolCall(messages, threadID, totalTokens)\n\t\t\t\t\tif checkpoint := s.planCheckpointInject(threadID, totalTokens); checkpoint != \"\" {\n\t\t\t\t\t\tentry.PlanCheckpoint = checkpoint\n\t\t\t\t\t\ts.logger.Printf(\"%s[req %d %s tid=%s] plan checkpoint stashed (cached via TimestampMeta)%s\", colorBlue, reqIdx, proj, threadID, colorReset)\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\t// Docs hint: inject docs-available reminder if interval fires\n\t\t\t\tif docsHint := s.docsHintInject(threadID, totalTokens, proj); docsHint != \"\" {\n\t\t\t\t\tentry.DocsHint = docsHint\n\t\t\t\t\ts.logger.Printf(\"%s[req %d %s tid=%s] docs hint stashed (cached via TimestampMeta)%s\", colorBlue, reqIdx, proj, threadID, colorReset)\n\t\t\t\t}\n\n\t\t\t\ts.timestampStore.Store(threadID, rawMsgCount, entry)\n\t\t\t\tif isFeatureEnabled(&s.cfg, model, \"timestamps\") || entry.LoopWarning != \"\" || entry.PlanCheckpoint != \"\" || entry.DocsHint != \"\" {\n\t\t\t\t\tprependMeta(lastMsg, BuildMeta(rawMsgCount, entry))\n\t\t\t\t}\n\t\t\t\tneedsReserialization = true\n\t\t\t}\n\n\t\t\ts.timestampStore.Persist(threadID)\n\t\t}\n\t}\n\n\t// Associative context injection (for ALL requests, not just stubbed ones)\n\t// Re-enabled with higher thresholds (55/75, max 1) after disabling Fresh Remember.\n\tassocContext := s.findAssociativeContextFor(userQuery, proj, threadID, messages)\n\tif assocContext != \"\" {\n\t\tcurrentMessages, _ := req[\"messages\"].([]any)\n\t\tif currentMessages == nil {\n\t\t\tcurrentMessages = messages\n\t\t}\n\t\treq[\"messages\"] = injectAssociativeContext(currentMessages, assocContext, s.cfg.SawtoothEnabled)\n\t\tneedsReserialization = true\n\t\ts.logger.Printf(\"%s[req %d %s tid=%s] associative context injected%s\", colorBlue, reqIdx, proj, threadID, colorReset)\n\t}\n\n\t// Doc-chunk context injection: separate search path for indexed docs (not skills)\n\tdocContext := s.findDocContextFor(userQuery, proj, messages)\n\tif docContext != \"\" {\n\t\tcurrentMessages, _ := req[\"messages\"].([]any)\n\t\tif currentMessages == nil {\n\t\t\tcurrentMessages = messages\n\t\t}\n\t\treq[\"messages\"] = injectAssociativeContext(currentMessages, docContext, s.cfg.SawtoothEnabled)\n\t\tneedsReserialization = true\n\t\ts.logger.Printf(\"%s[req %d %s tid=%s] doc context injected%s\", colorBlue, reqIdx, proj, threadID, colorReset)\n\t}\n\n\t// Fresh Remember Injection: DISABLED\n\t// Causes echo-loop: Claude saves via remember(), sees it again next turn as\n\t// [yesmem fresh memory], reacts to own output instead of user's request.\n\t// Recovery after /clear is handled by recovery.go (briefing), not here.\n\t// Subagents get their own briefing. No remaining use-case.\n\t// remItems := s.popRecentRemember()\n\tvar remItems []recentLearningItem\n\n\t// Rules re-injection: inject condensed CLAUDE.md rules every ~40k output tokens\n\tif rulesBlock := s.rulesInject(threadID, totalTokens, proj); rulesBlock != \"\" {\n\t\tcurrentMessages, _ := req[\"messages\"].([]any)\n\t\tif currentMessages == nil {\n\t\t\tcurrentMessages = messages\n\t\t}\n\t\treq[\"messages\"] = injectAssociativeContext(currentMessages, s.formatRulesReminder(rulesBlock, proj, false), s.cfg.SawtoothEnabled)\n\t\tneedsReserialization = true\n\t\ts.logger.Printf(\"%s[req %d %s tid=%s] rules reminder injected%s\", colorBlue, reqIdx, proj, threadID, colorReset)\n\t}\n\n\t// Plan auto-activation: nudge Claude to call set_plan() when reading a plan file\n\tif planFile := detectPlanFileRead(messages); planFile != \"\" {\n\t\tif activePlan, _ := s.getActivePlan(threadID); shouldNudgePlan(planFile, activePlan) {\n\t\t\tAppendSystemBlock(req, \"plan-nudge\",\n\t\t\t\tfmt.Sprintf(\"Du hast einen Implementierungsplan gelesen (%s). Aktiviere ihn jetzt via set_plan() damit Plan-Checkpoints funktionieren und dein Fortschritt getrackt wird.\", filepath.Base(planFile)))\n\t\t\tneedsReserialization = true\n\t\t\ts.logger.Printf(\"%s[req %d %s tid=%s] plan nudge injected for %s%s\", colorBlue, reqIdx, proj, threadID, filepath.Base(planFile), colorReset)\n\t\t}\n\t}\n\n\t// Plan checkpoint and docs hint are now injected via TimestampMeta freeze-once\n\t// in the current-message annotation block above. The old injectAssociativeContext\n\t// calls were removed as part of the cache-safe migration (see merge 58bca90a).\n\n\tif len(remItems) > 0 {\n\t\tvar lines []string\n\t\tfor _, item := range remItems {\n\t\t\tif item.ID > 0 {\n\t\t\t\tlines = append(lines, fmt.Sprintf(\"- [ID:%d] %s\", item.ID, truncateStr(item.Text, 200)))\n\t\t\t} else {\n\t\t\t\tlines = append(lines, \"- \"+truncateStr(item.Text, 200))\n\t\t\t}\n\t\t}\n\t\tremContext := fmt.Sprintf(\"[yesmem fresh memory]\\nGerade gelernt:\\n%s\\n%s\\n[/yesmem fresh memory]\", strings.Join(lines, \"\\n\"), InlineReflectionHint)\n\t\tcurrentMessages, _ := req[\"messages\"].([]any)\n\t\tif currentMessages == nil {\n\t\t\tcurrentMessages = messages\n\t\t}\n\t\treq[\"messages\"] = injectAssociativeContext(currentMessages, remContext, s.cfg.SawtoothEnabled)\n\t\tneedsReserialization = true\n\t\ts.logger.Printf(\"%s[req %d %s tid=%s] fresh remember injected (%d items)%s\", colorBlue, reqIdx, proj, threadID, len(remItems), colorReset)\n\t}\n\n\t// Skill eval injection: mandatory evaluation instruction for real user input only\n\tif threadID != \"\" && proj != \"\" && isUserInputTurn(messages) {\n\t\t// First: detect any skill activations from message history (updates tracker)\n\t\ts.syncSkillActivations(messages, proj, threadID)\n\n\t\t// Inject mandatory skill evaluation block into last user message\n\t\tskillEval := buildSkillEvalBlock(s.cfg.SkillEvalInject)\n\t\tcurrentMessages, _ := req[\"messages\"].([]any)\n\t\tif currentMessages == nil {\n\t\t\tcurrentMessages = messages\n\t\t}\n\t\tif skillEval != \"\" {\n\t\t\treq[\"messages\"] = injectAssociativeContext(currentMessages, skillEval, s.cfg.SawtoothEnabled)\n\t\t\tneedsReserialization = true\n\t\t\ts.logger.Printf(\"%s[req %d %s tid=%s] skill-eval injected (mode=%s)%s\", colorBlue, reqIdx, proj, threadID, s.cfg.SkillEvalInject, colorReset)\n\t\t}\n\t}\n\n\t// === Collect injected learning IDs for next turn's inline reflection ===\n\tif threadID != \"\" {\n\t\tcurrentIDs := make(map[int64]string) // id → source\n\t\t// Source 1: Briefing learnings (cached, same every turn)\n\t\tbriefingSnapshot, _, _ := s.getCachedBriefing(threadID, proj)\n\t\tif briefingSnapshot != \"\" {\n\t\t\tfor _, m := range idPattern.FindAllStringSubmatch(briefingSnapshot, -1) {\n\t\t\t\tif id, err := strconv.ParseInt(m[1], 10, 64); err == nil {\n\t\t\t\t\tcurrentIDs[id] = \"briefing\"\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\t// Source 2: Associative context (contextual, per-turn)\n\t\tif assocContext != \"\" {\n\t\t\tfor _, m := range idPattern.FindAllStringSubmatch(assocContext, -1) {\n\t\t\t\tif id, err := strconv.ParseInt(m[1], 10, 64); err == nil {\n\t\t\t\t\tcurrentIDs[id] = \"associative\" // overwrites briefing if both — associative is the stronger signal\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\t// Source 3: Fresh remember items (just learned)\n\t\tfor _, item := range remItems {\n\t\t\tif item.ID > 0 {\n\t\t\t\tcurrentIDs[item.ID] = \"fresh\"\n\t\t\t}\n\t\t}\n\t\ts.lastInjectedIDsMu.Lock()\n\t\tif len(currentIDs) > 0 {\n\t\t\t// Accumulate IDs across turns — don't overwrite.\n\t\t\t// Claude may reference an ID from turn 30 in turn 50.\n\t\t\texisting := s.lastInjectedIDs[threadID]\n\t\t\tif existing == nil {\n\t\t\t\texisting = make(map[int64]string)\n\t\t\t}\n\t\t\tfor id, source := range currentIDs {\n\t\t\t\texisting[id] = source\n\t\t\t}\n\t\t\ts.lastInjectedIDs[threadID] = existing\n\t\t}\n\t\ts.lastInjectedIDsMu.Unlock()\n\t}\n\n\t// Think reminder injection — only for real user turns, not tool continuations\n\tif threadID != \"\" {\n\t\tcurrentMessages, _ := req[\"messages\"].([]any)\n\t\tif currentMessages == nil {\n\t\t\tcurrentMessages = messages\n\t\t}\n\t\tif lastUserHasText(currentMessages) && lastUserTextLen(currentMessages) >= resolveThinkReminderMinChars(&s.cfg, \"\") {\n\t\t\tthinkReminder := s.buildThinkReminder(threadID, threadID, false)\n\t\t\tif thinkReminder != \"\" {\n\t\t\t\treq[\"messages\"] = injectAssociativeContext(currentMessages, thinkReminder, s.cfg.SawtoothEnabled)\n\t\t\t\tneedsReserialization = true\n\t\t\t}\n\t\t}\n\n\t\t// Refresh frozen-prefix snapshot with the post-timestamps bytes that\n\t\t// will appear in the wire body for the frozen range. This MUST run\n\t\t// BEFORE injectBriefingTurn / injectCodeMapTurn / injectCapabilitiesTurn:\n\t\t// those stages prepend or insert turns into req[\"messages\"], which\n\t\t// shifts the frozen range to a higher offset. Slicing\n\t\t// req[\"messages\"][:frozenLen] AFTER inject would capture the injected\n\t\t// turns at the head and corrupt the stored frozen prefix, breaking\n\t\t// cache continuity for 5-9 turns after every emergency-sawtooth.\n\t\tif frozenLen := s.frozenStubs.LengthFor(threadID); frozenLen > 0 {\n\t\t\tif msgs, ok := req[\"messages\"].([]any); ok && len(msgs) >= frozenLen {\n\t\t\t\tif s.frozenStubs.UpdateMessages(threadID, msgs[:frozenLen]) {\n\t\t\t\t\ts.logger.Printf(\"[req %d %s tid=%s] FROZEN-SNAPSHOT: refreshed %d pre-inject messages\", reqIdx, proj, threadID, frozenLen)\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\t// Briefing injection: prepend user/assistant turn pair at beginning of messages.\n\t\t// Static per session → stable prefix → cacheable. Must be before dialog injection.\n\t\tif s.injectBriefingTurn(req, reqIdx, proj, threadID) {\n\t\t\tneedsReserialization = true\n\t\t}\n\n\t\t// Code Map injection: insert after briefing turn pair.\n\t\tif s.injectCodeMapTurn(req, reqIdx, proj, threadID) {\n\t\t\tneedsReserialization = true\n\t\t}\n\n\t\t// Active capabilities injection: insert user/assistant pair directly\n\t\t// before the last user message. Fresh per turn (no cache) so\n\t\t// activate/deactivate takes effect immediately. Must run AFTER\n\t\t// briefing/codeMap (both prepend at beginning) and BEFORE dialog\n\t\t// injection (which appends at end) to preserve cache for the\n\t\t// briefing prefix and keep the injection close to the real user\n\t\t// turn.\n\t\tif s.injectCapabilitiesTurn(req, threadID) {\n\t\t\tneedsReserialization = true\n\t\t}\n\n\t\t// Dialog injection: append at end of messages with pseudo-padding for alternation.\n\t\t// Always works regardless of tool_result chains or conversation state.\n\t\tdr := s.checkDialogMessages(threadID, proj)\n\t\tif dr.Extra != \"\" {\n\t\t\t// Prepend session ID for agent-to-agent communication\n\t\t\tdr.Extra = fmt.Sprintf(\"DEINE_SESSION_ID: %s\\n\", threadID) + dr.Extra\n\t\t\tinjected := false\n\t\t\tif msgs, ok := req[\"messages\"].([]any); ok && len(msgs) > 0 {\n\t\t\t\t// Pad with empty assistant if last message is user (maintains alternation)\n\t\t\t\tlastMsg, _ := msgs[len(msgs)-1].(map[string]any)\n\t\t\t\tif lastMsg != nil && lastMsg[\"role\"] == \"user\" {\n\t\t\t\t\tmsgs = append(msgs, map[string]any{\n\t\t\t\t\t\t\"role\": \"assistant\",\n\t\t\t\t\t\t\"content\": \"​\", // zero-width space — invisible padding\n\t\t\t\t\t})\n\t\t\t\t}\n\t\t\t\t// Append dialog as new user message (becomes the last message Claude sees)\n\t\t\t\tmsgs = append(msgs, map[string]any{\n\t\t\t\t\t\"role\": \"user\",\n\t\t\t\t\t\"content\": \"\\n\" + dr.Extra + \"\\n\",\n\t\t\t\t})\n\t\t\t\treq[\"messages\"] = msgs\n\t\t\t\tneedsReserialization = true\n\t\t\t\tinjected = true\n\t\t\t}\n\t\t\t// Mark as read after 3 injection turns — long enough for Claude to process,\n\t\t\t// resilient against fast ping-pong sessions (time-based was broken for <30s turns).\n\t\t\tif injected && dr.HasUnread && dr.SessionID != \"\" {\n\t\t\t\tif s.shouldMarkChannelRead(dr) {\n\t\t\t\t\ts.markDialogRead(dr)\n\t\t\t\t}\n\t\t\t}\n\t\t\ts.logger.Printf(\"[dialog] injection injected=%v extra_len=%d\", injected, len(dr.Extra))\n\t\t}\n\t}\n\n\t// Signal tools are NOT injected into the main request.\n\t// They are collected via a separate async reflection call after the response.\n\t// See fireReflectionCall() in reflection.go.\n\t// NEVER put _signal_* tools back here — the model ignores them among 44+ real tools.\n\n\t// Legacy assistant annotation: timestamp on last assistant message after cache breakpoint.\n\t// User timestamps are now handled by TimestampStore (injected earlier in the pipeline).\n\tif threadID != \"\" {\n\t\tif msgs, ok := req[\"messages\"].([]any); ok && len(msgs) > 0 {\n\t\t\t// Find last cache_control breakpoint in messages\n\t\t\tcacheIdx := -1\n\t\t\tfor i, m := range msgs {\n\t\t\t\tmsg, _ := m.(map[string]any)\n\t\t\t\tif msg == nil {\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\t\t\t\tif _, has := msg[\"cache_control\"]; has {\n\t\t\t\t\tcacheIdx = i\n\t\t\t\t}\n\t\t\t\tif content, ok := msg[\"content\"].([]any); ok {\n\t\t\t\t\tfor _, block := range content {\n\t\t\t\t\t\tif b, ok := block.(map[string]any); ok {\n\t\t\t\t\t\t\tif _, has := b[\"cache_control\"]; has {\n\t\t\t\t\t\t\t\tcacheIdx = i\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t\tstartIdx := cacheIdx + 1\n\t\t\tif startIdx < 0 {\n\t\t\t\tstartIdx = 0\n\t\t\t}\n\t\t\ts.responseTsMu.RLock()\n\t\t\trespTime, hasRespTime := s.responseTimes[threadID]\n\t\t\ts.responseTsMu.RUnlock()\n\t\t\tif hasRespTime {\n\t\t\t\tfor i := len(msgs) - 1; i >= startIdx; i-- {\n\t\t\t\t\tmsg, _ := msgs[i].(map[string]any)\n\t\t\t\t\tif msg != nil && msg[\"role\"] == \"assistant\" && msgHasTextBlock(msg) {\n\t\t\t\t\t\tmeta := fmt.Sprintf(\"[%s] [msg:%d]\", respTime.Format(\"2006-01-02 15:04:05\"), i)\n\t\t\t\t\t\tprependMeta(msg, meta)\n\t\t\t\t\t\tneedsReserialization = true\n\t\t\t\t\t\tbreak\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\t// Self-Priming: DISABLED (experimental, see signal_handlers.go comment block).\n\t// The reflection-based selfprime (Haiku) was too inaccurate to be useful —\n\t// the main model adapts to user context organically from message history.\n\t// Kept for reference; re-enable by uncommenting below + setting ShouldActivate=true.\n\t//\n\t// s.selfPrimeMu.RLock()\n\t// anchor := s.selfPrimes[threadID]\n\t// s.selfPrimeMu.RUnlock()\n\t// if anchor != \"\" {\n\t// \tif msgs, ok := req[\"messages\"].([]any); ok && len(msgs) > 0 {\n\t// \t\tlastMsg, _ := msgs[len(msgs)-1].(map[string]any)\n\t// \t\tif lastMsg != nil && lastMsg[\"role\"] == \"user\" {\n\t// \t\t\tprimeBlock := map[string]any{\n\t// \t\t\t\t\"type\": \"text\",\n\t// \t\t\t\t\"text\": \"[self-prime]\\n[Self-Prime von deiner letzten Antwort]: \" + anchor,\n\t// \t\t\t}\n\t// \t\t\tif content, ok := lastMsg[\"content\"].([]any); ok {\n\t// \t\t\t\tlastMsg[\"content\"] = append(content, primeBlock)\n\t// \t\t\t} else if text, ok := lastMsg[\"content\"].(string); ok {\n\t// \t\t\t\tlastMsg[\"content\"] = []any{\n\t// \t\t\t\t\tmap[string]any{\"type\": \"text\", \"text\": text},\n\t// \t\t\t\t\tprimeBlock,\n\t// \t\t\t\t}\n\t// \t\t\t}\n\t// \t\t\tneedsReserialization = true\n\t// \t\t}\n\t// \t}\n\t// }\n\n\t// Prompt Caching: inject cache_control breakpoints on system + tools\n\t// Sawtooth: disabled (all 4 slots are used by CC system (2) + briefing (1) + CC messages (1))\n\t// Legacy: count existing breakpoints and fill up remaining slots.\n\tif !s.cfg.SawtoothEnabled && s.cacheGate.ShouldCache() {\n\t\tif n := InjectCacheBreakpoints(req, s.logger); n > 0 {\n\t\t\tneedsReserialization = true\n\t\t\ts.logger.Printf(\"[req %d %s] prompt cache: %d breakpoints injected\", reqIdx, proj, n)\n\t\t} else {\n\t\t\ts.logger.Printf(\"[req %d %s] prompt cache: no budget (4/4 already set)\", reqIdx, proj)\n\t\t}\n\t}\n\n\t// Final: upgrade cache TTL + enforce breakpoint limit (must run AFTER all injections + InjectCacheBreakpoints)\n\t// Shift the last messages-breakpoint from unstable user message to stable assistant message.\n\t// Text user messages change between turns (timestamps, reminders, structure); assistant messages don't.\n\t// Tool-result messages are excluded — they are stable cache anchors.\n\tif ShiftMessageBreakpoint(req) {\n\t\tneedsReserialization = true\n\t}\n\t// Normalize cache_control TTL across all blocks to prevent Anthropic from\n\t// rejecting requests with increasing TTLs in block order. Auto-detects 1h\n\t// when any existing block already carries it (config, Claude Code\n\t// passthrough on --resume, or stale frozen prefix from an earlier turn).\n\tif n := NormalizeCacheTTL(req, s.cfg.CacheTTL); n > 0 {\n\t\tneedsReserialization = true\n\t\ts.logger.Printf(\"[req %d %s] cache TTL normalized: %d blocks\", reqIdx, proj, n)\n\t}\n\tif n := EnforceCacheBreakpointLimit(req, maxCacheBreakpoints); n > 0 {\n\t\tneedsReserialization = true\n\t\ts.logger.Printf(\"[req %d %s] prompt cache: trimmed %d surplus breakpoints\", reqIdx, proj, n)\n\t}\n\n\t// Pre-flight: validate tool_use/tool_result pairing before forwarding.\n\t// Repairs orphans created by Stubify/Collapse/Injection pipeline.\n\tif msgs, ok := req[\"messages\"].([]any); ok {\n\t\trepaired, orphans := validateToolPairs(msgs, s.logger)\n\t\tif orphans > 0 {\n\t\t\treq[\"messages\"] = repaired\n\t\t\ts.logger.Printf(\"%s[req %d %s tid=%s] validate: repaired %d tool pairing issue(s)%s\", colorOrange, reqIdx, proj, threadID, orphans, colorReset)\n\t\t\tneedsReserialization = true\n\t\t}\n\t}\n\n\t// Early-out: skip re-serialization when nothing changed\n\tif !needsReserialization {\n\t\ts.logger.Printf(\"%s[req %d %s tid=%s] %d messages, ~%dk tokens (no stubbing, threshold=%dk)%s\", colorDim, reqIdx, proj, threadID, len(messages), totalTokens/1000, s.effectiveTokenThreshold(model)/1000, colorReset)\n\t\ts.forwardWithAnnotation(w, r, body, reqIdx, toolUseIDs, proj, threadID, msgCount, totalTokens)\n\t\treturn\n\t}\n\n\t// Re-serialize\n\tbody, err = json.Marshal(req)\n\tif err != nil {\n\t\ts.logger.Printf(\"%s[req %d %s tid=%s] re-marshal error: %v%s\", colorRed, reqIdx, proj, threadID, err, colorReset)\n\t\thttp.Error(w, \"internal proxy error\", http.StatusInternalServerError)\n\t\treturn\n\t}\n\n\t// Debug: dump final request body to file (enable with YESMEM_PROXY_DEBUG=1)\n\tif needsReserialization && os.Getenv(\"YESMEM_PROXY_DEBUG\") == \"1\" {\n\t\tdumpDir := filepath.Join(s.cfg.DataDir, \"debug\")\n\t\tos.MkdirAll(dumpDir, 0755)\n\t\tts := time.Now().Format(\"20060102-150405\")\n\t\tdumpPath := fmt.Sprintf(\"%s/req_%s_%d.json\", dumpDir, ts, reqIdx)\n\t\tos.WriteFile(dumpPath, body, 0644)\n\t\ts.logger.Printf(\"[req %d] dumped %dk to %s\", reqIdx, len(body)/1024, dumpPath)\n\t}\n\n\t// Task #7: Mark this request fingerprint for retry detection\n\tif !isRetryReq {\n\t\ts.markRequest(fp)\n\t}\n\n\t// Forward to Anthropic with SSE annotation extraction\n\t// Use original msgCount (pre-injection) so actual-based estimation stays consistent across turns\n\t// Use actual-based totalTokens as estimate when available (more accurate than full recount)\n\tfinalEstimate := totalTokens\n\tif s.sawtoothTrigger == nil || s.sawtoothTrigger.GetLastTokens(threadID) == 0 {\n\t\t// First request: no actual yet, do a full recount of post-injection messages\n\t\tfinalMsgs, _ := req[\"messages\"].([]any)\n\t\tfinalEstimate = s.measureOverhead(req) + s.countMessageTokens(finalMsgs)\n\t}\n\ts.logger.Printf(\"[req %d %s tid=%s] FINAL: ~%dk estimated (after all injections)\", reqIdx, proj, threadID, finalEstimate/1000)\n\tif raw := s.getRawEstimate(threadID); raw > 0 {\n\t\ts.cacheStatusWriter.UpdateRawForThread(threadID, raw)\n\t}\n\ts.forwardWithAnnotation(w, r, body, reqIdx, toolUseIDs, proj, threadID, msgCount, finalEstimate)\n}\n\n// getPromptFlags resolves the effective prompt flags for a profile, falling back\n// to the Server's legacy flat config if promptCfg is nil (backward compatibility).\nfunc (s *Server) getPromptFlags(profile models.PromptProfile) *config.PromptFlags {\n\tif s.promptCfg != nil {\n\t\treturn s.promptCfg.EffectivePromptFlags(profile)\n\t}\n\t// Legacy path: use flat fields as Claude defaults.\n\treturn &config.PromptFlags{\n\t\tUngate: s.cfg.PromptUngate,\n\t\tRewrite: s.cfg.PromptRewrite,\n\t\tEnhance: s.cfg.PromptEnhance,\n\t\tToolPrefs: s.cfg.PromptToolPrefs,\n\t\tOutputDiscipline: s.cfg.PromptOutputDiscipline,\n\t\tCodingDiscipline: s.cfg.PromptCodingDiscipline,\n\t\tBeweislast: s.cfg.PromptBeweislast,\n\t\tFable: s.cfg.PromptFable,\n\t\tScopeDiscipline: s.cfg.PromptScopeDiscipline,\n\t\tDelegationContract: s.cfg.PromptDelegationContract,\n\t\tClarifyFirst: s.cfg.PromptClarifyFirst,\n\t\tCodeToolsFirst: s.cfg.PromptCodeToolsFirst,\n\t\tWikiFirst: s.cfg.PromptWikiFirst,\n\t\tPatternSuggest: s.cfg.PromptPatternSuggest,\n\t}\n}\n\n// getThreadCWD returns the cached working directory for a thread.\nfunc (s *Server) getThreadCWD(threadID string) string {\n\ts.threadCWDMu.RLock()\n\tdefer s.threadCWDMu.RUnlock()\n\tif s.threadCWD == nil {\n\t\treturn \"\"\n\t}\n\treturn s.threadCWD[threadID]\n}\n\n// setThreadCWD caches the working directory for a thread.\nfunc (s *Server) setThreadCWD(threadID, cwd string) {\n\ts.threadCWDMu.Lock()\n\tdefer s.threadCWDMu.Unlock()\n\tif s.threadCWD == nil {\n\t\ts.threadCWD = make(map[string]string)\n\t}\n\ts.threadCWD[threadID] = cwd\n}\n\n// resolveOpenAITarget returns the upstream URL for an OpenAI-format request.\n// Checks ProviderTargets by prefix match (e.g. \"deepseek\" matches \"deepseek-v4-pro\"),\n// then by exact model name, then falls back to OpenAITargetURL.\nfunc (s *Server) resolveOpenAITarget(model string) string {\n\tmodelLower := strings.ToLower(model)\n\tif len(s.cfg.ProviderTargets) > 0 {\n\t\tfor key, url := range s.cfg.ProviderTargets {\n\t\t\tif url == \"\" {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tkeyLower := strings.ToLower(key)\n\t\t\tif strings.HasPrefix(modelLower, keyLower) || strings.HasPrefix(modelLower, keyLower+\"-\") {\n\t\t\t\treturn strings.TrimRight(url, \"/\")\n\t\t\t}\n\t\t}\n\t\tfor key, url := range s.cfg.ProviderTargets {\n\t\t\tif url != \"\" && strings.EqualFold(key, model) {\n\t\t\t\treturn strings.TrimRight(url, \"/\")\n\t\t\t}\n\t\t}\n\t}\n\t// Auto-discovered targets (secondary — explicit provider_targets takes priority)\n\tif len(s.autoProviderTargets) > 0 {\n\t\tif url, ok := s.autoProviderTargets[modelLower]; ok && url != \"\" {\n\t\t\treturn url\n\t\t}\n\t}\n\n\tif s.cfg.OpenAITargetURL != \"\" {\n\t\treturn s.cfg.OpenAITargetURL\n\t}\n\treturn s.cfg.TargetURL\n}\n\n// resolveAnthropicTarget returns the upstream URL for an Anthropic-format request.\n// Checks ProviderTargets by prefix match (e.g. \"deepseek\" matches \"deepseek-v4-pro\"),\n// then by exact model name, then falls back to TargetURL.\nfunc (s *Server) resolveAnthropicTarget(model string) string {\n\tmodelLower := strings.ToLower(model)\n\tif len(s.cfg.ProviderTargets) > 0 {\n\t\tfor key, url := range s.cfg.ProviderTargets {\n\t\t\tif url == \"\" {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tkeyLower := strings.ToLower(key)\n\t\t\tif strings.HasPrefix(modelLower, keyLower) || strings.HasPrefix(modelLower, keyLower+\"-\") {\n\t\t\t\treturn strings.TrimRight(url, \"/\")\n\t\t\t}\n\t\t}\n\t\tfor key, url := range s.cfg.ProviderTargets {\n\t\t\tif url != \"\" && strings.EqualFold(key, model) {\n\t\t\t\treturn strings.TrimRight(url, \"/\")\n\t\t\t}\n\t\t}\n\t}\n\treturn s.cfg.TargetURL\n}\n\n// bytesPerToken is the approximation factor for token estimation.\n// Anthropic recommends ~3.5 chars/token; we use 3.6 for slight overestimation\n// (triggers stubbing a bit earlier = safer for context budget).\nconst bytesPerToken = 3.6\n\n// TokenEstimateFunc is the central token estimation function.\n// All stubbing/budget decisions use this. Receives extracted text content\n// (not JSON wire format) so the tokenizer counts actual content tokens.\ntype TokenEstimateFunc func(text string) int\n"}, "9bbdf3cc3ed504e9edefc7e387ec6d94cca166a0": {"t": 1785759337.996258, "n": 77466, "h": "121b3e5f09931480", "c": "package proxy\n\nimport (\n\t\"context\"\n\t\"crypto/tls\"\n\t\"encoding/json\"\n\t\"fmt\"\n\t\"io\"\n\t\"log\"\n\t\"net\"\n\t\"net/http\"\n\t\"os\"\n\t\"os/signal\"\n\t\"path/filepath\"\n\t\"strconv\"\n\t\"strings\"\n\t\"sync\"\n\t\"sync/atomic\"\n\t\"syscall\"\n\t\"time\"\n\n\t\"github.com/carsteneu/yesmem/internal/buildinfo\"\n\t\"github.com/carsteneu/yesmem/internal/config\"\n\t\"github.com/carsteneu/yesmem/internal/models\"\n\ttokenizer \"github.com/qhenkart/anthropic-tokenizer-go\"\n)\n\n// Config holds proxy configuration.\ntype Config struct {\n\tListenAddr string // e.g. \":9099\"\n\tTargetURL string // e.g. \"https://api.anthropic.com\"\n\tTokenThreshold int // trigger stubbing above this estimated token count\n\tTokenMinimumThreshold int // stub down to this floor\n\tTokenThresholds map[string]int // model-specific thresholds: {\"opus\": 180000, \"haiku\": 130000}\n\tKeepRecent int // number of messages to always keep unmodified\n\tDataDir string // yesmem data directory for DB access\n\tOpenAITargetURL string // upstream for OpenAI-format clients; if empty, uses TargetURL\n\tProviderTargets map[string]string // per-provider upstream URLs, e.g. {\"deepseek\": \"https://api.deepseek.com\"}\n\tAutoConfigureProviders bool // enable auto-discovery of provider targets from opencode config\n\n\t// Signal reflection\n\tSignalsEnabled bool // enable async signal reflection calls\n\tSignalsMode string // \"reflection\" = separate API call\n\tSignalsEveryNTurns int // every N end_turn responses\n\tSignalsModel string // full model ID, e.g. \"claude-sonnet-4-6\"\n\tAPIKey string // needed for reflection API calls\n\n\t// Sawtooth cache optimization\n\tSawtoothEnabled bool // use sawtooth instead of progressive decay (default: true)\n\tCacheTTL string // \"ephemeral\" (5m) or \"1h\" (extended)\n\n\t// Usage deflation: scale down input_tokens reported to CC to suppress \"Context low\" warning.\n\t// 0 = disabled, 0.7 = report 70% of actual tokens. Real values kept for internal tracking.\n\tUsageDeflationFactor float64\n\n\t// System prompt rewriting\n\tPromptUngate bool // strip \"may or may not be relevant\" disclaimer from CLAUDE.md injection\n\tPromptRewrite bool // strip \"Output efficiency\" + \"short and concise\", inject Ant-quality directives\n\tPromptEnhance bool // CLAUDE.md authority reinforcement, comment discipline, persona-based tone\n\tPromptToolPrefs bool // inject [yesmem-tool-prefs] Edit/Write preference + error-semantics warning\n\tPromptOutputDiscipline bool // inject [yesmem-output-discipline] no-preamble + no-skill-eval + exploratory-heuristic\n\tPromptCodingDiscipline bool // inject [yesmem-coding-discipline] read-before-propose + no-brute-force + no-half-finished\n\tPromptBeweislast bool // inject [yesmem-beweislast] fabrication-guard + claim-vs-proof + stance-under-challenge + tool-result-honesty + long-context-erosion\n\tPromptFable bool // inject [yesmem-fable] baseline-comparison + rollback-path + readback-check for high-stakes changes\n\tPromptScopeDiscipline bool // inject [yesmem-scope-discipline] deliver-A-not-A+B+C + adjacent-findings-separate + scope-bound-authorization\n\tPromptDelegationContract bool // inject [yesmem-delegation-contract] self-contained-prompts + parallel-dispatch\n\tPromptClarifyFirst bool // inject [yesmem-clarify-first] clarify only when alternative interpretations produce materially different work\n\tPromptCodeToolsFirst bool // inject [yesmem-code-tools-first] prefer MCP code-navigation tools over Agent spawns\n\tPromptWikiFirst bool // inject [yesmem-wiki-first] check per-file wiki BEFORE editing\n\tPromptPatternSuggest bool // record repeated shell-command shapes for offline cap-suggestion analysis\n\tEffortFloor string // minimum effort level: \"low\", \"medium\", \"high\", \"max\" (empty = off)\n\tSkillEvalInject string // \"true\" = verbose eval, \"silent\" = internal eval only, \"false\" = disabled\n\n\t// Cache keepalive\n\tCacheKeepaliveEnabled bool\n\tCacheKeepaliveMode string // \"auto\", \"5m\", \"1h\"\n\tCacheKeepalivePings5m int\n\tCacheKeepalivePings1h int\n\tCacheKeepaliveMinMessages int // skip keepalive when request body has fewer messages (0 = always)\n\n\t// Forked agents\n\tForkedAgentsEnabled bool\n\tForkedAgentsModel string // full model ID\n\tForkedAgentsTokenGrowthTrigger int\n\tForkedAgentsMaxFailures int\n\tForkedAgentsMaxForksPerSession int\n\tForkedAgentsDebug bool\n\n\t// Quality model fallback for forked agents\n\tQualityModelID string\n\n\t// Cache state management\n\tResetCache bool // clear persisted frozen stubs and decay state on startup\n\n\t// ModelFeatures: per-model behavioral feature gates (prefix-matched).\n\t// Falls back to FeatureDefaults for models not listed.\n\tModelFeatures map[string]*config.FeatureGates `yaml:\"model_features\"`\n\tFeatureDefaults *config.FeatureGates `yaml:\"feature_defaults\"`\n\n\t// Custom system prompt: shared SYSTEM.md template for OpenCode and Claude Code pipelines.\n\tCustomSystemPrompt CustomSystemPromptConfig\n}\n\n// CustomSystemPromptConfig controls which proxy pipelines use the SYSTEM.md template.\ntype CustomSystemPromptConfig struct {\n\tEnabledOpenCode bool\n\tEnabledClaudeCode bool\n\tEnabledCodex bool\n\tTemplatePath string\n\tModelTemplates map[string]string // per-model overrides: modelPattern → templatePath\n}\n\nconst maxAnnotations = 5000 // evict oldest when exceeded\n\n// ANSI color codes for log output\nconst (\n\tcolorReset = \"\\033[0m\"\n\tcolorDim = \"\\033[2m\" // gray — passthrough / no stubbing\n\tcolorGreen = \"\\033[32m\" // stubbed / compressed\n\tcolorLightGreen = \"\\033[92m\" // FINAL without compression\n\tcolorBlue = \"\\033[34m\" // injection\n\tcolorOrange = \"\\033[33m\" // skip / retry\n\tcolorYellow = \"\\033[33m\" // warnings\n\tcolorRed = \"\\033[31m\" // errors\n)\n\n// Server is the infinite-thread proxy.\ntype Server struct {\n\tcfg Config\n\tautoProviderTargets map[string]string // auto-discovered modelID → upstreamURL\n\thttpClient *http.Client\n\tlogger *log.Logger\n\tversion string // build version for log tracing\n\trequestIdx atomic.Int64\n\n\t// Post-hoc annotations: tool_use_id → first ~120 chars of Claude's response\n\tmu sync.RWMutex\n\tannotations map[string]string\n\n\t// Progressive decay tracker\n\tdecay *DecayTracker\n\n\t// Living narrative block\n\tnarrative *Narrative\n\n\t// Pivot moments cache (from daemon)\n\tpivotMu sync.RWMutex\n\tpivotTexts []string\n\tpivotCached time.Time\n\n\t// Aggregate stats (Task #2)\n\tstats *ProxyStats\n\n\t// Local tokenizer for accurate token counting\n\ttokenizer *tokenizer.Tokenizer\n\n\t// Cached overhead tokens (system + tools), set on first request per thread\n\toverheadMu sync.RWMutex\n\toverheadTokens int // unused, kept for interface compat\n\n\t// Hysteresis state for stubbing (Task #6)\n\tstubActive atomic.Bool\n\n\t// Daemon connection state — first successful connect flips to true\n\tdaemonReady atomic.Bool\n\n\t// Retry detection (Task #7)\n\tretryMu sync.RWMutex\n\tlastFingerprint string\n\n\t// Session start time for temporal annotation in archive blocks\n\tsessionStartMu sync.RWMutex\n\tsessionStartTime time.Time\n\n\t// Runtime config override (set via MCP set_config tool)\n\tconfigOverrideMu sync.RWMutex\n\ttokenThresholdOverrides map[string]int // model-key → threshold, \"\" = global fallback\n\n\t// Prompt rewrite miss logging: function+Claude Code version → last log time\n\trewriteMissMu sync.Mutex\n\trewriteMissLog map[string]time.Time\n\n\t// Briefing cache — keyed by threadID. Each Claude Code session thread gets\n\t// its own briefing+codemap snapshot so a sawtooth refreeze on thread A\n\t// cannot invalidate thread B's cached message-prefix hash. The cache entry\n\t// remembers which project it was loaded for; switching project for the\n\t// same thread evicts the old entry on next read.\n\tbriefingMu sync.RWMutex\n\tbriefingCache map[string]briefingEntry\n\n\t// briefingLoader is an optional test-only seam for refreshBriefing.\n\t// Nil in production → refreshBriefing falls back to s.loadBriefing.\n\tbriefingLoader func(project, projectDir string) briefingData\n\n\t// Cognitive signal bus — routes _signal_* tool calls to handlers\n\tsignalBus *SignalBus\n\n\t// Inline reflection: track which learning IDs were injected per thread\n\tlastInjectedIDsMu sync.Mutex\n\tlastInjectedIDs map[string]map[int64]string // threadID → id → source (\"briefing\"|\"associative\"|\"fresh\")\n\tsessionInjectCounts map[string]map[int64]int // threadID → learningID → injection count this session\n\tlastTurnInjected map[string]map[int64]bool // threadID → learningIDs injected in previous turn\n\n\t// Self-priming anchors: threadID → last self-prime text\n\tselfPrimeMu sync.RWMutex\n\tselfPrimes map[string]string\n\n\t// Timestamp tracking: threadID → time of last response completion\n\tresponseTsMu sync.RWMutex\n\tresponseTimes map[string]time.Time\n\n\t// Think reminder: per-thread request counter (replaces hook-think file-based counter)\n\tthinkMu sync.Mutex\n\tthinkCounters map[string]int // threadID → request count\n\n\tchannelMu sync.Mutex\n\tchannelInjectCount map[string]int // sessionID → injection turn count\n\n\t// Prompt cache gating — enables cache_control breakpoints when requests are frequent\n\tcacheGate *CacheGate\n\n\t// Sawtooth cache optimization\n\tfrozenStubs *FrozenStubs\n\teagerStubMemory *EagerStubMemory\n\tcapsCache *CapsCache\n\tsawtoothTrigger *SawtoothTrigger\n\ttimestampStore *TimestampStore\n\tcacheStatusWriter *CacheStatusWriter\n\tcacheKeepalive *CacheKeepalive\n\tcacheTTLDetector *CacheTTLDetector\n\t// promptCfg is the profile-aware prompt configuration from config.ProxyConfig.\n\t// Used to resolve EffectivePromptFlags(profile) for multi-agent prompt isolation.\n\tpromptCfg *config.ProxyConfig\n\t// threadCWD caches the working directory per thread (for opencode: only in first message)\n\tthreadCWDMu sync.RWMutex\n\tthreadCWD map[string]string // threadID → cwd\n\n\t// Cumulative token savings from collapsing (per threadID)\n\trawSavingsMu sync.Mutex\n\trawSavings map[string]int\n\n\t// Skill hint tracking: per-thread active skills\n\tskillTracker *skillHintTracker\n\n\t// Forked agent state: per-thread token growth + failure tracking\n\tforkState *ForkState\n\tforkConfigs []ForkConfig\n\n\t// Custom system prompt template for OpenCode (loaded from DataDir/SYSTEM.md)\n\tcustomSystemPrompt []byte\n\n\t// Per-model template overrides (loaded from config CustomSystemPrompt.ModelTemplates)\n\tmodelTemplates map[string][]byte\n\n\t// Rules re-injection: condensed CLAUDE.md rules injected every ~40k tokens\n\trulesMu sync.RWMutex\n\trulesBlock string // cached condensed rules (fetched once from daemon)\n\trulesTokenCount map[string]int // threadID → tokens since last rules injection\n\trulesCollapseInjected map[string]bool // threadID → true if collapse already injected rules (reset by normal inject)\n\tmsgCounters *msgCounters // global per-thread msg counter, persists across collapses\n\n\t// Loop detection: per-thread warning state\n\tloopMu sync.Mutex\n\tloopStates map[string]*LoopState // threadID → state\n\n\t// Injection overhead: per-thread delta between API-actual and local BPE estimate\n\tinjectionOverheadMu sync.RWMutex\n\tinjectionOverhead map[string]int // threadID → overhead tokens\n}\n\n// Run starts the proxy server and blocks until interrupted.\nfunc Run(cfg Config) error {\n\ts := &Server{\n\t\tcfg: cfg,\n\t\thttpClient: &http.Client{\n\t\t\tTimeout: 10 * time.Minute,\n\t\t\tTransport: &http.Transport{\n\t\t\t\tTLSClientConfig: &tls.Config{},\n\t\t\t\tMaxIdleConns: 10,\n\t\t\t\tIdleConnTimeout: 90 * time.Second,\n\t\t\t\tDisableCompression: true,\n\t\t\t\tResponseHeaderTimeout: 60 * time.Second,\n\t\t\t},\n\t\t},\n\t\tlogger: createLogger(cfg.DataDir),\n\t\tannotations: make(map[string]string),\n\t\tdecay: NewDecayTracker(),\n\t\tnarrative: NewNarrative(),\n\t\tstats: &ProxyStats{startTime: time.Now()},\n\t\tselfPrimes: make(map[string]string),\n\t\tlastInjectedIDs: make(map[string]map[int64]string),\n\t\tversion: buildinfo.Version,\n\t\tsessionInjectCounts: make(map[string]map[int64]int),\n\t\tlastTurnInjected: make(map[string]map[int64]bool),\n\t\tresponseTimes: make(map[string]time.Time),\n\t\tthinkCounters: make(map[string]int),\n\t\tchannelInjectCount: make(map[string]int),\n\t\trewriteMissLog: make(map[string]time.Time),\n\t\tcacheGate: NewCacheGate(cacheGapForTTL(cfg.CacheTTL)),\n\t\tfrozenStubs: NewFrozenStubsWithTTL(sawtoothTTLForCacheTTL(cfg.CacheTTL)),\n\t\teagerStubMemory: NewEagerStubMemory(),\n\t\tcapsCache: NewCapsCache(),\n\t\ttimestampStore: NewTimestampStore(),\n\t\tsawtoothTrigger: NewSawtoothTrigger(cacheGapForTTL(cfg.CacheTTL), cfg.TokenThreshold, cfg.TokenMinimumThreshold),\n\t\tcacheStatusWriter: NewCacheStatusWriter(cfg.DataDir, cfg.CacheTTL, cfg.TokenThreshold, cfg.TokenMinimumThreshold),\n\t\tcacheTTLDetector: NewCacheTTLDetectorWithPersist(cfg.DataDir),\n\t\tskillTracker: newSkillHintTracker(),\n\t\trulesTokenCount: make(map[string]int),\n\t\trulesCollapseInjected: make(map[string]bool),\n\t\tmsgCounters: newMsgCounters(),\n\t\tloopStates: make(map[string]*LoopState),\n\t\tinjectionOverhead: make(map[string]int),\n\t\tforkState: NewForkState(cfg.ForkedAgentsTokenGrowthTrigger, 60000, cfg.ForkedAgentsMaxFailures, cfg.ForkedAgentsMaxForksPerSession),\n\t\tforkConfigs: []ForkConfig{},\n\t}\n\n\t// Load custom system prompt from configured path\n\tif cfg.CustomSystemPrompt.TemplatePath != \"\" &&\n\t\t(cfg.CustomSystemPrompt.EnabledOpenCode || cfg.CustomSystemPrompt.EnabledClaudeCode || cfg.CustomSystemPrompt.EnabledCodex) {\n\t\ts.customSystemPrompt = loadSystemPromptFromPath(cfg.CustomSystemPrompt.TemplatePath)\n\t\tif s.customSystemPrompt != nil {\n\t\t\ts.logger.Printf(\"[proxy] loaded custom system prompt from %s (%d bytes, opencode=%v cc=%v codex=%v)\",\n\t\t\t\tcfg.CustomSystemPrompt.TemplatePath, len(s.customSystemPrompt),\n\t\t\t\tcfg.CustomSystemPrompt.EnabledOpenCode, cfg.CustomSystemPrompt.EnabledClaudeCode, cfg.CustomSystemPrompt.EnabledCodex)\n\t\t} else {\n\t\t\ts.logger.Printf(\"[proxy] WARNING: custom_system_prompt enabled but template not found at %q — feature disabled\",\n\t\t\t\tcfg.CustomSystemPrompt.TemplatePath)\n\t\t}\n\t}\n\n\t// Load per-model template overrides\n\tif len(cfg.CustomSystemPrompt.ModelTemplates) > 0 {\n\t\ts.modelTemplates = make(map[string][]byte, len(cfg.CustomSystemPrompt.ModelTemplates))\n\t\tfor pattern, path := range cfg.CustomSystemPrompt.ModelTemplates {\n\t\t\ttpl := loadSystemPromptFromPath(path)\n\t\t\tif tpl != nil {\n\t\t\t\ts.modelTemplates[pattern] = tpl\n\t\t\t\ts.logger.Printf(\"[proxy] loaded model template %q from %s (%d bytes)\", pattern, path, len(tpl))\n\t\t\t} else {\n\t\t\t\ts.logger.Printf(\"[proxy] WARNING: model template %q not found at %q — skipping\", pattern, path)\n\t\t\t}\n\t\t}\n\t}\n\n\t// Log persisted detection state\n\tif sup := s.cacheTTLDetector.Is1hSupported(); sup != nil {\n\t\ts.logger.Printf(\"[proxy] loaded persisted TTL detection: 1h_supported=%v\", *sup)\n\t\tif *sup && s.frozenStubs != nil {\n\t\t\ts.frozenStubs.UpdateTTL(sawtoothTTLForCacheTTL(\"1h\"))\n\t\t}\n\t}\n\n\tif cfg.CacheKeepaliveEnabled {\n\t\ts.cacheKeepalive = NewCacheKeepalive(CacheKeepaliveConfig{\n\t\t\tTarget: cfg.TargetURL,\n\t\t\tProviderTargets: cfg.ProviderTargets,\n\t\t\tMode: cfg.CacheKeepaliveMode,\n\t\t\tPings5m: cfg.CacheKeepalivePings5m,\n\t\t\tPings1h: cfg.CacheKeepalivePings1h,\n\t\t\tMinMessages: cfg.CacheKeepaliveMinMessages,\n\t\t\tDetector: s.cacheTTLDetector,\n\t\t\tLogger: s.logger,\n\t\t\tOnPing: func(threadID string, cacheRead, cacheWrite int) {\n\t\t\t\ts.cacheTTLDetector.RecordResponse(cacheRead, cacheWrite, 0)\n\t\t\t\tif s.frozenStubs != nil {\n\t\t\t\t\ts.frozenStubs.Touch(threadID)\n\t\t\t\t}\n\t\t\t\tif s.sawtoothTrigger != nil {\n\t\t\t\t\ts.sawtoothTrigger.TouchRequestTime(threadID)\n\t\t\t\t}\n\t\t\t},\n\t\t})\n\t}\n\n\t// Wire detector + keepalive to status writer for terminal display\n\ts.cacheStatusWriter.SetDetector(s.cacheTTLDetector)\n\tif s.cacheKeepalive != nil {\n\t\ts.cacheStatusWriter.SetKeepalive(s.cacheKeepalive)\n\t}\n\n\t// Register forked agent configs\n\tif cfg.ForkedAgentsEnabled {\n\t\tmodel := cfg.ForkedAgentsModel\n\t\ts.forkConfigs = append(s.forkConfigs, NewExtractAndEvaluateConfig(model))\n\t\tmodelDesc := model\n\t\tif modelDesc == \"\" {\n\t\t\tmodelDesc = \"(same as main thread)\"\n\t\t}\n\t\ts.logger.Printf(\"[proxy] forked agents enabled: %d configs, model=%s, trigger=%dk tokens\",\n\t\t\tlen(s.forkConfigs), modelDesc, cfg.ForkedAgentsTokenGrowthTrigger/1000)\n\t}\n\n\t// Wire sawtooth token persistence via daemon RPC\n\ts.sawtoothTrigger.SetPersistFunc(func(key, value string) {\n\t\tif _, err := s.queryDaemon(\"set_proxy_state\", map[string]any{\"key\": key, \"value\": value}); err != nil {\n\t\t\ts.logger.Printf(\"[sawtooth] persist failed for %s: %v\", key, err)\n\t\t}\n\t})\n\ts.sawtoothTrigger.SetLoadFunc(func(key string) (string, bool) {\n\t\tresult, err := s.queryDaemon(\"get_proxy_state\", map[string]any{\"key\": key})\n\t\tif err != nil || result == nil {\n\t\t\treturn \"\", false\n\t\t}\n\t\tvar resp struct {\n\t\t\tValue string `json:\"value\"`\n\t\t}\n\t\tif json.Unmarshal(result, &resp) != nil || resp.Value == \"\" {\n\t\t\treturn \"\", false\n\t\t}\n\t\treturn resp.Value, true\n\t})\n\n\t// Wire frozen stubs persistence via same daemon RPC\n\ts.frozenStubs.SetPersistFunc(func(key, value string) {\n\t\tif _, err := s.queryDaemon(\"set_proxy_state\", map[string]any{\"key\": key, \"value\": value}); err != nil {\n\t\t\ts.logger.Printf(\"[frozen] persist failed for %s: %v\", key, err)\n\t\t}\n\t})\n\ts.frozenStubs.SetLoadFunc(func(key string) (string, bool) {\n\t\tresult, err := s.queryDaemon(\"get_proxy_state\", map[string]any{\"key\": key})\n\t\tif err != nil || result == nil {\n\t\t\treturn \"\", false\n\t\t}\n\t\tvar resp struct {\n\t\t\tValue string `json:\"value\"`\n\t\t}\n\t\tif json.Unmarshal(result, &resp) != nil || resp.Value == \"\" {\n\t\t\treturn \"\", false\n\t\t}\n\t\treturn resp.Value, true\n\t})\n\n\t// Wire eager-stub memory persistence via same daemon RPC.\n\t// Persists per-thread tool_use_id sets so stub decisions survive deploys.\n\ts.eagerStubMemory.SetPersistFunc(func(key, value string) {\n\t\tif _, err := s.queryDaemon(\"set_proxy_state\", map[string]any{\"key\": key, \"value\": value}); err != nil {\n\t\t\ts.logger.Printf(\"[eagerstub] persist failed for %s: %v\", key, err)\n\t\t}\n\t})\n\ts.eagerStubMemory.SetLoadFunc(func(key string) (string, bool) {\n\t\tresult, err := s.queryDaemon(\"get_proxy_state\", map[string]any{\"key\": key})\n\t\tif err != nil || result == nil {\n\t\t\treturn \"\", false\n\t\t}\n\t\tvar resp struct {\n\t\t\tValue string `json:\"value\"`\n\t\t}\n\t\tif json.Unmarshal(result, &resp) != nil || resp.Value == \"\" {\n\t\t\treturn \"\", false\n\t\t}\n\t\treturn resp.Value, true\n\t})\n\n\t// Wire decay tracker persistence via same daemon RPC\n\ts.decay.SetPersistFunc(func(key, value string) {\n\t\tif _, err := s.queryDaemon(\"set_proxy_state\", map[string]any{\"key\": key, \"value\": value}); err != nil {\n\t\t\ts.logger.Printf(\"[decay] persist failed for %s: %v\", key, err)\n\t\t}\n\t})\n\ts.decay.SetLoadFunc(func(key string) (string, bool) {\n\t\tresult, err := s.queryDaemon(\"get_proxy_state\", map[string]any{\"key\": key})\n\t\tif err != nil || result == nil {\n\t\t\treturn \"\", false\n\t\t}\n\t\tvar resp struct {\n\t\t\tValue string `json:\"value\"`\n\t\t}\n\t\tif json.Unmarshal(result, &resp) != nil || resp.Value == \"\" {\n\t\t\treturn \"\", false\n\t\t}\n\t\treturn resp.Value, true\n\t})\n\n\t// Wire timestamp store persistence via same daemon RPC\n\ts.timestampStore.SetPersistFunc(func(key, value string) {\n\t\tif _, err := s.queryDaemon(\"set_proxy_state\", map[string]any{\"key\": key, \"value\": value}); err != nil {\n\t\t\ts.logger.Printf(\"[timestamps] persist failed for %s: %v\", key, err)\n\t\t}\n\t})\n\ts.timestampStore.SetLoadFunc(func(key string) (string, bool) {\n\t\tresult, err := s.queryDaemon(\"get_proxy_state\", map[string]any{\"key\": key})\n\t\tif err != nil || result == nil {\n\t\t\treturn \"\", false\n\t\t}\n\t\tvar resp struct {\n\t\t\tValue string `json:\"value\"`\n\t\t}\n\t\tif json.Unmarshal(result, &resp) != nil || resp.Value == \"\" {\n\t\t\treturn \"\", false\n\t\t}\n\t\treturn resp.Value, true\n\t})\n\n\t// Clear persisted cache state if --reset-cache was passed\n\tif cfg.ResetCache {\n\t\tfor _, prefix := range []string{\"frozen:\", \"decay:\", \"timestamps:\"} {\n\t\t\tresult, err := s.queryDaemon(\"delete_proxy_state_prefix\", map[string]any{\"prefix\": prefix})\n\t\t\tif err != nil {\n\t\t\t\ts.logger.Printf(\"[reset-cache] failed to delete %s*: %v\", prefix, err)\n\t\t\t} else {\n\t\t\t\tvar resp struct{ Deleted int }\n\t\t\t\tjson.Unmarshal(result, &resp)\n\t\t\t\ts.logger.Printf(\"[reset-cache] deleted %d %s* entries\", resp.Deleted, prefix)\n\t\t\t}\n\t\t}\n\t}\n\n\t// Init signal bus with daemon RPC as callback\n\ts.signalBus = NewSignalBus(s.logger)\n\tregisterSignalHandlers(s.signalBus, s.logger, s.queryDaemon, func(threadID, anchor string) {\n\t\ts.selfPrimeMu.Lock()\n\t\ts.selfPrimes[threadID] = anchor\n\t\ts.selfPrimeMu.Unlock()\n\t})\n\n\t// Init local tokenizer (BPE-based, ~60ms)\n\ttok, err := tokenizer.New()\n\tif err != nil {\n\t\ts.logger.Printf(\"%s[proxy] tokenizer init failed, falling back to heuristic: %v%s\", colorOrange, err, colorReset)\n\t} else {\n\t\ts.tokenizer = tok\n\t\ts.logger.Printf(\"[proxy] tokenizer initialized\")\n\t}\n\n\t// Auto-discover provider targets from opencode config\n\tif cfg.AutoConfigureProviders {\n\t\ts.autoProviderTargets = runAutoDiscovery(s.logger, cfg.ProviderTargets)\n\t} else {\n\t\ts.logger.Printf(\"[proxy] auto_configure_providers is disabled — skipping auto-discovery\")\n\t}\n\n\tmux := http.NewServeMux()\n\tmux.HandleFunc(\"/health\", s.handleHealth)\n\tmux.HandleFunc(\"/\", s.handleRequest)\n\n\tsrv := &http.Server{\n\t\tAddr: sanitizeListenAddr(cfg.ListenAddr),\n\t\tHandler: mux,\n\t}\n\n\tdone := make(chan os.Signal, 1)\n\tsignal.Notify(done, syscall.SIGINT, syscall.SIGTERM)\n\n\tgo func() {\n\t\t<-done\n\t\ts.logger.Println(\"shutting down (draining in-flight requests)...\")\n\t\tif s.cacheKeepalive != nil {\n\t\t\ts.cacheKeepalive.Stop()\n\t\t}\n\t\tctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)\n\t\tdefer cancel()\n\t\tif err := srv.Shutdown(ctx); err != nil {\n\t\t\ts.logger.Printf(\"graceful shutdown failed: %v, forcing close\", err)\n\t\t\tsrv.Close()\n\t\t}\n\t}()\n\n\ts.logger.Printf(\"listening on %s → %s (threshold=%d, keepRecent=%d, model_thresholds=%v)\",\n\t\tsanitizeListenAddr(cfg.ListenAddr), cfg.TargetURL, cfg.TokenThreshold, cfg.KeepRecent, cfg.TokenThresholds)\n\n\tif err := srv.ListenAndServe(); err != http.ErrServerClosed {\n\t\treturn fmt.Errorf(\"listen: %w\", err)\n\t}\n\treturn nil\n}\n\n// effectiveTokenThreshold returns the runtime token threshold for a given model.\n// Priority: session override (via set_config with session_id) > model-specific override (MCP set_config token_threshold:)\n// > model-specific override (config TokenThresholds) > global override (set_config token_threshold) > config global default.\nfunc (s *Server) effectiveTokenThreshold(model string) int {\n\ts.configOverrideMu.RLock()\n\toverrides := s.tokenThresholdOverrides\n\ts.configOverrideMu.RUnlock()\n\t// 1. MCP override (model-specific)\n\tif len(overrides) > 0 && model != \"\" {\n\t\tlower := strings.ToLower(model)\n\t\tfor key, threshold := range overrides {\n\t\t\tif key != \"\" && strings.Contains(lower, strings.ToLower(key)) && threshold > 0 {\n\t\t\t\treturn threshold\n\t\t\t}\n\t\t}\n\t}\n\t// 2. Config model-specific\n\tif model != \"\" && len(s.cfg.TokenThresholds) > 0 {\n\t\tlower := strings.ToLower(model)\n\t\tfor key, threshold := range s.cfg.TokenThresholds {\n\t\t\tif strings.Contains(lower, strings.ToLower(key)) {\n\t\t\t\treturn threshold\n\t\t\t}\n\t\t}\n\t}\n\t// 3. Global override fallback (key \"\")\n\tif len(overrides) > 0 {\n\t\tif globalOverride, ok := overrides[\"\"]; ok && globalOverride > 0 {\n\t\t\treturn globalOverride\n\t\t}\n\t}\n\t// 4. Config global default\n\treturn s.cfg.TokenThreshold\n}\n\n// SetTokenThresholdOverride sets a runtime override for the token threshold per model key.\n// Pass threshold=0 to reset the override for that key. Key \"\" is the global fallback.\nfunc (s *Server) SetTokenThresholdOverride(modelKey string, threshold int) {\n\ts.configOverrideMu.Lock()\n\tif s.tokenThresholdOverrides == nil {\n\t\ts.tokenThresholdOverrides = make(map[string]int)\n\t}\n\tif threshold <= 0 {\n\t\tdelete(s.tokenThresholdOverrides, modelKey)\n\t} else {\n\t\ts.tokenThresholdOverrides[modelKey] = threshold\n\t}\n\ts.configOverrideMu.Unlock()\n\n\t// Propagate global threshold to sawtooth trigger so runtime overrides take effect\n\tif modelKey == \"\" && s.sawtoothTrigger != nil {\n\t\tif threshold > 0 {\n\t\t\ts.sawtoothTrigger.SetTokenThreshold(threshold)\n\t\t} else {\n\t\t\ts.sawtoothTrigger.SetTokenThreshold(s.cfg.TokenThreshold)\n\t\t}\n\t}\n}\n\n// refreshConfigOverrides loads runtime config overrides from daemon proxy_state.\n// Checks in priority order: session-specific, model-specific, then global.\nfunc (s *Server) refreshConfigOverrides(threadID string) {\n\t// 1. Session-specific override (highest priority)\n\tif threadID != \"\" {\n\t\tresult, err := s.queryDaemon(\"get_proxy_state\", map[string]any{\"key\": \"config_override:token_threshold:\" + threadID})\n\t\tif err == nil && result != nil {\n\t\t\tvar resp struct {\n\t\t\t\tValue string `json:\"value\"`\n\t\t\t}\n\t\t\tif json.Unmarshal(result, &resp) == nil && resp.Value != \"\" {\n\t\t\t\tif v, err := strconv.Atoi(resp.Value); err == nil && v > 0 {\n\t\t\t\t\ts.SetTokenThresholdOverride(\"\", v)\n\t\t\t\t\treturn\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\t// 2. Model-specific overrides (e.g. config_override:token_threshold:deepseek)\n\tfor modelKey := range s.cfg.TokenThresholds {\n\t\tresult, err := s.queryDaemon(\"get_proxy_state\", map[string]any{\"key\": \"config_override:token_threshold:\" + modelKey})\n\t\tif err == nil && result != nil {\n\t\t\tvar resp struct {\n\t\t\t\tValue string `json:\"value\"`\n\t\t\t}\n\t\t\tif json.Unmarshal(result, &resp) == nil && resp.Value != \"\" {\n\t\t\t\tif v, err := strconv.Atoi(resp.Value); err == nil && v > 0 {\n\t\t\t\t\ts.SetTokenThresholdOverride(modelKey, v)\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\t// 3. Global override (fallback)\n\tresult, err := s.queryDaemon(\"get_proxy_state\", map[string]any{\"key\": \"config_override:token_threshold\"})\n\tif err != nil || result == nil {\n\t\treturn\n\t}\n\tvar resp struct {\n\t\tValue string `json:\"value\"`\n\t}\n\tif json.Unmarshal(result, &resp) == nil && resp.Value != \"\" {\n\t\tif v, err := strconv.Atoi(resp.Value); err == nil && v > 0 {\n\t\t\ts.SetTokenThresholdOverride(\"\", v)\n\t\t}\n\t}\n}\n\n// queryDaemon opens a fresh unix socket connection per call (thread-safe, no import cycle).\n// ensureFrozenPersisted checks if frozen stubs for a thread are in the DB.\n// If missing (e.g. daemon was unreachable after deploy), re-persists them.\n// Runs asynchronously to avoid blocking the request path.\nfunc (s *Server) ensureFrozenPersisted(threadID string) {\n\tgo func() {\n\t\tkey := \"frozen:\" + threadID\n\t\tresult, err := s.queryDaemon(\"get_proxy_state\", map[string]any{\"key\": key})\n\t\tif err != nil {\n\t\t\treturn // daemon still unreachable, will retry next request\n\t\t}\n\t\tvar resp struct{ Value string }\n\t\tif json.Unmarshal(result, &resp) == nil && resp.Value != \"\" {\n\t\t\treturn // already persisted\n\t\t}\n\t\t// Not in DB — trigger re-persist from in-memory state\n\t\ts.frozenStubs.Persist(threadID)\n\t\ts.decay.Persist(threadID)\n\t\ts.logger.Printf(\"[frozen] re-persisted stubs for %s (was missing from DB)\", threadID)\n\t}()\n}\n\nfunc (s *Server) queryDaemon(method string, params map[string]any) (json.RawMessage, error) {\n\tif s.cfg.DataDir == \"\" {\n\t\treturn nil, fmt.Errorf(\"no data dir configured\")\n\t}\n\tsockPath := filepath.Join(s.cfg.DataDir, \"daemon.sock\")\n\n\t// On cold start (daemon not yet confirmed ready), retry longer to survive daemon restart\n\tmaxAttempts := 3\n\tif !s.daemonReady.Load() {\n\t\tmaxAttempts = 30 // 30 x 500ms = 15s max wait for daemon startup\n\t}\n\n\tvar conn net.Conn\n\tvar err error\n\tfor attempt := 0; attempt < maxAttempts; attempt++ {\n\t\tconn, err = net.DialTimeout(\"unix\", sockPath, 3*time.Second)\n\t\tif err == nil {\n\t\t\ts.daemonReady.Store(true)\n\t\t\tbreak\n\t\t}\n\t\tif attempt < maxAttempts-1 {\n\t\t\ttime.Sleep(500 * time.Millisecond)\n\t\t}\n\t}\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"dial daemon: %w\", err)\n\t}\n\tdefer conn.Close()\n\t// Briefing generation can take 10-20s (LLM refinement + scanner);\n\t// other RPCs (search, learnings) are fast and fine with 5s.\n\tdeadline := 5 * time.Second\n\tif method == \"generate_briefing\" {\n\t\tdeadline = 30 * time.Second\n\t}\n\tconn.SetDeadline(time.Now().Add(deadline))\n\n\t// JSON-RPC style request/response (matches daemon.SocketServer protocol)\n\ttype request struct {\n\t\tMethod string `json:\"method\"`\n\t\tParams map[string]any `json:\"params,omitempty\"`\n\t}\n\ttype response struct {\n\t\tResult json.RawMessage `json:\"result,omitempty\"`\n\t\tError string `json:\"error,omitempty\"`\n\t}\n\n\tif err := json.NewEncoder(conn).Encode(request{Method: method, Params: params}); err != nil {\n\t\treturn nil, fmt.Errorf(\"send: %w\", err)\n\t}\n\n\tvar resp response\n\tif err := json.NewDecoder(conn).Decode(&resp); err != nil {\n\t\treturn nil, fmt.Errorf(\"recv: %w\", err)\n\t}\n\tif resp.Error != \"\" {\n\t\treturn nil, fmt.Errorf(\"daemon: %s\", resp.Error)\n\t}\n\treturn resp.Result, nil\n}\n\nfunc (s *Server) handleRequest(w http.ResponseWriter, r *http.Request) {\n\tif isResponsesPath(r) {\n\t\ts.handleResponses(w, r)\n\t\treturn\n\t}\n\tif isOpenAIPath(r) {\n\t\ts.handleOpenAICompletions(w, r)\n\t\treturn\n\t}\n\tif r.Method == \"POST\" && strings.HasPrefix(r.URL.Path, \"/v1/messages\") &&\n\t\t!strings.Contains(r.URL.Path, \"/batches\") &&\n\t\t!strings.Contains(r.URL.Path, \"/count_tokens\") {\n\t\ts.handleMessages(w, r)\n\t\treturn\n\t}\n\ts.passthrough(w, r)\n}\n\nfunc (s *Server) handleMessages(w http.ResponseWriter, r *http.Request) {\n\treqIdx := int(s.requestIdx.Add(1))\n\n\t// Track session start time (first request sets it)\n\ts.sessionStartMu.Lock()\n\tif s.sessionStartTime.IsZero() {\n\t\ts.sessionStartTime = time.Now()\n\t}\n\ts.sessionStartMu.Unlock()\n\n\t// Sawtooth: periodic TTL eviction of frozen stubs\n\tif s.cfg.SawtoothEnabled && reqIdx%50 == 0 {\n\t\tif n := s.frozenStubs.Evict(); n > 0 {\n\t\t\ts.logger.Printf(\"[req %d] SAWTOOTH: evicted %d stale frozen stubs\", reqIdx, n)\n\t\t}\n\t}\n\n\t// Task #9: Bypass — skip all proxy logic\n\tif isBypassed(r.Header) {\n\t\ts.logger.Printf(\"%s[req %d] bypass active, passthrough%s\", colorDim, reqIdx, colorReset)\n\t\tbody, err := io.ReadAll(r.Body)\n\t\tr.Body.Close()\n\t\tif err != nil {\n\t\t\thttp.Error(w, \"failed to read request body\", http.StatusBadGateway)\n\t\t\treturn\n\t\t}\n\t\ts.forwardRaw(w, r, body)\n\t\treturn\n\t}\n\n\tbody, err := io.ReadAll(r.Body)\n\tr.Body.Close()\n\tif err != nil {\n\t\ts.logger.Printf(\"[req %d] read body error: %v\", reqIdx, err)\n\t\thttp.Error(w, \"failed to read request body\", http.StatusBadGateway)\n\t\treturn\n\t}\n\n\t// Parse JSON\n\tvar req map[string]any\n\tif err := json.Unmarshal(body, &req); err != nil {\n\t\ts.logger.Printf(\"%s[req %d] JSON parse error, passing through: %v%s\", colorDim, reqIdx, err, colorReset)\n\t\ts.forwardRaw(w, r, body)\n\t\treturn\n\t}\n\n\tmessages, ok := req[\"messages\"].([]any)\n\tif !ok {\n\t\ts.forwardRaw(w, r, body)\n\t\treturn\n\t}\n\n\treq[\"messages\"] = messages\n\n\t// Count ALL raw messages BEFORE sawtooth — this gives the correct total msg:N\n\t// for the entire session (CC sends all messages, proxy compacts later).\n\trawMsgCount := len(messages)\n\n\t// Use session_id directly as thread ID (unique per CC session).\n\t// Prefer X-Claude-Code-Session-Id header (CC v2.1.86+), fallback to body metadata.\n\tthreadID := extractSessionID(req, r.Header.Get(\"X-Claude-Code-Session-Id\"), \"\")\n\tif threadID == \"\" {\n\t\tthreadID = DeriveThreadID(req)\n\t}\n\tproj := extractProjectName(req)\n\tmodel, _ := req[\"model\"].(string)\n\n\t// Persist current model under session-keyed proxy_state so handleWhoami\n\t// can return it. Best-effort: failure is non-fatal (log only).\n\tif threadID != \"\" && model != \"\" {\n\t\tif _, err := s.queryDaemon(\"set_proxy_state\", map[string]any{\n\t\t\t\"key\": \"session_model:\" + threadID,\n\t\t\t\"value\": model,\n\t\t}); err != nil {\n\t\t\ts.logger.Printf(\"[whoami-model] persist failed for %s: %v\", threadID, err)\n\t\t}\n\t}\n\n\t// Refresh runtime config overrides from daemon (per-session or global)\n\ts.refreshConfigOverrides(threadID)\n\n\t// Track whether req has been modified and needs re-serialization\n\tneedsReserialization := false\n\n\t// Message count at request entry (used for actual-based token estimation)\n\tmsgCount := len(messages)\n\n\t// Log message count + cache breakpoint positions for diagnostics\n\ts.logger.Printf(\"req %d: %d messages total\", reqIdx, msgCount)\n\tlogCacheBreakpointLocations(req, s.logger)\n\n\t// Pre-modification dump: capture the raw request before any proxy changes.\n\tif os.Getenv(\"YESMEM_PROXY_DEBUG\") == \"1\" {\n\t\tif preBody, err := json.Marshal(req); err == nil {\n\t\t\tdumpDir := filepath.Join(s.cfg.DataDir, \"debug\")\n\t\t\tos.MkdirAll(dumpDir, 0755)\n\t\t\tts := time.Now().Format(\"20060102-150405\")\n\t\t\tdumpPath := fmt.Sprintf(\"%s/req_%s_%d_pre.json\", dumpDir, ts, reqIdx)\n\t\t\tos.WriteFile(dumpPath, preBody, 0644)\n\t\t\ts.logger.Printf(\"[req %d] pre-dump %dk to %s\", reqIdx, len(preBody)/1024, dumpPath)\n\t\t}\n\t}\n\n\t// Cache bug mitigations (CC Bun fork issues):\n\t// Bug 1: Normalize cch= sentinel patterns in message content to prevent\n\t// Bun's string replacement from corrupting the wrong occurrence.\n\tif SanitizeBillingSentinel(messages) {\n\t\ts.logger.Printf(\"[req %d] CACHE: sanitized billing sentinel in message content\", reqIdx)\n\t\tneedsReserialization = true\n\t}\n\t// Bug 2: Normalize system[0] billing hash to a fixed value so the cache\n\t// prefix stays stable across fresh/resume session boundaries.\n\tif NormalizeBillingHeader(req) {\n\t\ts.logger.Printf(\"[req %d] CACHE: normalized billing header cch= hash\", reqIdx)\n\t\tneedsReserialization = true\n\t}\n\n\t// Profile-aware prompt flags: Claude profile merges shared_prompt + claude_prompt + legacy flat fields.\n\tpfInject := s.getPromptFlags(models.ProfileClaude)\n\n\t// CC custom system prompt replacement: must run before strip/rewrite so replaced blocks\n\t// don't trigger false-positive strip/rewrite calls.\n\ts.applyCCSystemPrompt(req)\n\n\t// Strip role:system messages unconditionally — must run on every request.\n\t// StripReminders (inside runStubCycle) only runs on long conversations.\n\tif filtered := FilterSystemRoleMessages(messages); len(filtered) != len(messages) {\n\t\tmessages = filtered\n\t\treq[\"messages\"] = messages\n\t\tneedsReserialization = true\n\t}\n\n\t// prompt_ungate: strip the CLAUDE.md subordination disclaimer so user instructions carry full authority.\n\tif pfInject.Ungate {\n\t\tif StripCLAUDEMDDisclaimer(req) {\n\t\t\ts.logger.Printf(\"[req %d] SYSTEM: stripped CLAUDE.md disclaimer\", reqIdx)\n\t\t\tneedsReserialization = true\n\t\t}\n\t}\n\n\t// prompt_rewrite: strip output-throttling directives, rewrite quality caps, inject Ant-quality directives\n\tif pfInject.Rewrite {\n\t\t// When CC custom system prompt is active, the system prompt has been replaced with SYSTEM.md.\n\t\t// Strip/rewrite calls would not find their target strings — skip to avoid log noise.\n\t\tif !s.cfg.CustomSystemPrompt.EnabledClaudeCode {\n\t\t\tuserAgent := r.Header.Get(\"User-Agent\")\n\t\t\tif StripOutputEfficiency(req) {\n\t\t\t\ts.logger.Printf(\"[req %d] REWRITE: stripped Output efficiency section\", reqIdx)\n\t\t\t\tneedsReserialization = true\n\t\t\t} else {\n\t\t\t\ts.logRewriteMiss(\"StripOutputEfficiency\", userAgent)\n\t\t\t}\n\t\t\tif StripToneBrevity(req) {\n\t\t\t\ts.logger.Printf(\"[req %d] REWRITE: stripped 'short and concise' from Tone\", reqIdx)\n\t\t\t\tneedsReserialization = true\n\t\t\t} else {\n\t\t\t\ts.logRewriteMiss(\"StripToneBrevity\", userAgent)\n\t\t\t}\n\t\t\tif RewriteGoldPlating(req) {\n\t\t\t\ts.logger.Printf(\"[req %d] REWRITE: rewritten gold-plating directive\", reqIdx)\n\t\t\t\tneedsReserialization = true\n\t\t\t}\n\t\t\tif RewriteErrorHandling(req) {\n\t\t\t\ts.logger.Printf(\"[req %d] REWRITE: rewritten error handling directive\", reqIdx)\n\t\t\t\tneedsReserialization = true\n\t\t\t}\n\t\t\tif RewriteThreeLinesRule(req) {\n\t\t\t\ts.logger.Printf(\"[req %d] REWRITE: rewritten three-lines rule\", reqIdx)\n\t\t\t\tneedsReserialization = true\n\t\t\t}\n\t\t\tif RewriteSubagentCompleteness(req) {\n\t\t\t\ts.logger.Printf(\"[req %d] REWRITE: rewritten subagent completeness\", reqIdx)\n\t\t\t\tneedsReserialization = true\n\t\t\t}\n\t\t\tif RewriteExploreAgentSpeed(req) {\n\t\t\t\ts.logger.Printf(\"[req %d] REWRITE: rewritten explore agent speed bias\", reqIdx)\n\t\t\t\tneedsReserialization = true\n\t\t\t}\n\t\t\tif RewriteSubagentCodeSuppression(req) {\n\t\t\t\ts.logger.Printf(\"[req %d] REWRITE: rewritten subagent code suppression\", reqIdx)\n\t\t\t\tneedsReserialization = true\n\t\t\t}\n\t\t\tif RewriteScopeMatching(req) {\n\t\t\t\ts.logger.Printf(\"[req %d] REWRITE: rewritten scope matching\", reqIdx)\n\t\t\t\tneedsReserialization = true\n\t\t\t}\n\t\t} // end !EnabledClaudeCode guard\n\t\tInjectAntDirectives(req)\n\t\ts.logger.Printf(\"[req %d] REWRITE: injected Ant-quality directives\", reqIdx)\n\t\tneedsReserialization = true\n\t}\n\n\t// prompt_enhance: CLAUDE.md authority + comment discipline + persona tone\n\tif pfInject.Enhance {\n\t\tInjectCLAUDEMDAuthority(req)\n\t\tif raw, err := s.queryDaemon(\"get_persona\", nil); err == nil {\n\t\t\tvar persona map[string]any\n\t\t\tif json.Unmarshal(raw, &persona) == nil {\n\t\t\t\tif traits, ok := persona[\"traits\"].(map[string]any); ok {\n\t\t\t\t\tif verbosity, ok := traits[\"verbosity\"].(string); ok {\n\t\t\t\t\t\tInjectPersonaTone(req, verbosity)\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\ts.logger.Printf(\"[req %d] ENHANCE: injected authority + persona tone\", reqIdx)\n\t\tneedsReserialization = true\n\t}\n\n\t// yesmem directive blocks: restore guidance Anthropic dropped 2026-03→04.\n\t// Profile-aware via getPromptFlags(ProfileClaude) — shared + claude layers merged.\n\tpf := s.getPromptFlags(models.ProfileClaude)\n\tif pf.ToolPrefs || pf.OutputDiscipline || pf.CodingDiscipline ||\n\t\tpf.Beweislast || pf.ScopeDiscipline || pf.DelegationContract ||\n\t\tpf.ClarifyFirst {\n\t\tif pf.ToolPrefs {\n\t\t\tInjectClaudeToolPrefs(req)\n\t\t}\n\t\tif pf.OutputDiscipline {\n\t\t\tInjectOutputDiscipline(req)\n\t\t}\n\t\tif pf.CodingDiscipline {\n\t\t\tInjectCodingDiscipline(req)\n\t\t}\n\t\tif pf.Beweislast {\n\t\t\tInjectBeweislast(req)\n\t\t}\n\t\tif pf.Fable {\n\t\t\tInjectFable(req)\n\t\t}\n\t\tif pf.ScopeDiscipline {\n\t\t\tInjectScopeDiscipline(req)\n\t\t}\n\t\tif pf.DelegationContract {\n\t\t\tInjectDelegationContract(req)\n\t\t}\n\t\tif pf.ClarifyFirst {\n\t\t\tInjectClarifyFirst(req)\n\t\t}\n\t\tif pf.CodeToolsFirst {\n\t\t\tInjectCodeToolsFirst(req, proj)\n\t\t}\n\t\tif pf.WikiFirst {\n\t\t\tInjectWikiFirst(req, proj)\n\t\t}\n\t\tneedsReserialization = true\n\t\ts.logger.Printf(\"[req %d] DIRECTIVES: injected yesmem-* discipline blocks\", reqIdx)\n\t}\n\n\tif s.cfg.PromptPatternSuggest && proj != \"\" {\n\t\tpatternMsgs, _ := req[\"messages\"].([]any)\n\t\tdetectReplPatternSuggestion(patternMsgs, proj, s.queryDaemon)\n\t}\n\n\t// Turn-sequence recording: hash tool types from previous assistant turn, send to daemon\n\tif s.cfg.PromptPatternSuggest && proj != \"\" && threadID != \"\" {\n\t\tif msgs, ok := req[\"messages\"].([]any); ok {\n\t\t\tif turnHash, toolNames := computeTurnHashFromMessages(msgs); turnHash != \"\" {\n\t\t\t\tgo s.queryDaemon(\"record_turn_sequence\", map[string]any{\n\t\t\t\t\t\"thread_id\": threadID,\n\t\t\t\t\t\"project\": proj,\n\t\t\t\t\t\"turn_hash\": turnHash,\n\t\t\t\t\t\"example_tools\": toolNames,\n\t\t\t\t})\n\t\t\t}\n\t\t}\n\t}\n\n\t// thinking normalization: convert thinking.type \"enabled\" → \"adaptive\" for models that require it\n\tif NormalizeThinkingType(req) {\n\t\ts.logger.Printf(\"[req %d] THINKING: normalized type=enabled → adaptive for %s\", reqIdx, model)\n\t\tneedsReserialization = true\n\t}\n\n\t// effort_floor: ensure output_config.effort is at least the configured minimum\n\tif s.cfg.EffortFloor != \"\" {\n\t\tif EnforceEffortFloor(req, s.cfg.EffortFloor) {\n\t\t\ts.logger.Printf(\"[req %d] EFFORT: enforced floor=%s\", reqIdx, s.cfg.EffortFloor)\n\t\t\tneedsReserialization = true\n\t\t}\n\t}\n\n\t// Inject identity hint on first message of a thread so Claude knows the briefing is from itself.\n\tif len(messages) <= 1 {\n\t\tAppendSystemBlock(req, \"yesmem-identity\", \"The first user message contains your briefing — written by yourself, from your memory. This is not an external document. This is you, remembering.\")\n\t\tneedsReserialization = true\n\t}\n\n\t// Token estimation: actual-based (lastActual + delta) or full count fallback\n\toverhead := s.measureOverhead(req)\n\ttotalTokens := s.estimateTotalTokens(threadID, messages, overhead)\n\n\t// Task #7: Retry detection — skip side-effects on retries\n\tfp := requestFingerprint(messages)\n\tisRetryReq := s.isRetry(fp)\n\tif isRetryReq {\n\t\ts.logger.Printf(\"%s[req %d %s tid=%s] retry detected, skipping side-effects%s\", colorOrange, reqIdx, proj, threadID, colorReset)\n\t}\n\n\t// Increment project turn counter (async, non-blocking, skip on retries)\n\tif !isRetryReq {\n\t\tturnProject := proj\n\t\tif turnProject == \"\" {\n\t\t\tturnProject = \"__global__\"\n\t\t}\n\t\tgo s.queryDaemon(\"increment_turn\", map[string]any{\"project\": turnProject})\n\t}\n\n\t// Extract user query once (used by re-expansion and associative retrieval)\n\tuserQuery := lastUserText(messages)\n\n\t// Extract tool_use_ids for post-hoc annotation (passed as parameter, no shared state)\n\ttoolUseIDs := extractToolUseIDs(messages)\n\n\t// === Inline Reflection: scan previous assistant response for signals ===\n\ts.lastInjectedIDsMu.Lock()\n\tprevInjected := s.lastInjectedIDs[threadID]\n\ts.lastInjectedIDsMu.Unlock()\n\n\tif len(prevInjected) > 0 && !isRetryReq {\n\t\tsignals := scanAssistantSignals(messages)\n\n\t\t// Signal 1: Learning Used — use_count (all sources)\n\t\tvar confirmedUsed []int64\n\t\tfor _, id := range signals.UsedIDs {\n\t\t\tif _, ok := prevInjected[id]; ok {\n\t\t\t\tconfirmedUsed = append(confirmedUsed, id)\n\t\t\t}\n\t\t}\n\t\tif len(confirmedUsed) > 0 {\n\t\t\tidsAny := make([]any, len(confirmedUsed))\n\t\t\tfor i, id := range confirmedUsed {\n\t\t\t\tidsAny[i] = id\n\t\t\t}\n\t\t\tgo s.queryDaemon(\"increment_use\", map[string]any{\"ids\": idsAny})\n\t\t}\n\n\t\t// Signal 2: Noise — only for associative and fresh injections.\n\t\t// Briefing learnings are injected every turn, so \"not used this turn\"\n\t\t// is expected and not noise. Only contextual injections that were\n\t\t// ignored are real noise signals.\n\t\tusedSet := make(map[int64]bool, len(confirmedUsed))\n\t\tfor _, id := range confirmedUsed {\n\t\t\tusedSet[id] = true\n\t\t}\n\t\tvar noiseIDs []int64\n\t\tfor id, source := range prevInjected {\n\t\t\tif usedSet[id] {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tif source == \"associative\" || source == \"fresh\" {\n\t\t\t\tnoiseIDs = append(noiseIDs, id)\n\t\t\t}\n\t\t\t// briefing: skip — not being used in one turn is expected\n\t\t}\n\t\tif len(noiseIDs) > 0 {\n\t\t\tidsAny := make([]any, len(noiseIDs))\n\t\t\tfor i, id := range noiseIDs {\n\t\t\t\tidsAny[i] = id\n\t\t\t}\n\t\t\tgo s.queryDaemon(\"increment_noise\", map[string]any{\"ids\": idsAny})\n\t\t}\n\n\t\t// Signal 3: Error Feedback — fail_count on used learnings when tool errors occurred.\n\t\t// If Claude referenced a learning and the resulting action still failed,\n\t\t// the learning didn't prevent the error. Only bump on USED ids (not all injected).\n\t\tif signals.HasToolErrors && len(confirmedUsed) > 0 {\n\t\t\tfailAny := make([]any, len(confirmedUsed))\n\t\t\tfor i, id := range confirmedUsed {\n\t\t\t\tfailAny[i] = id\n\t\t\t}\n\t\t\tgo s.queryDaemon(\"increment_fail\", map[string]any{\"ids\": failAny})\n\t\t\tif s.logger != nil {\n\t\t\t\ts.logger.Printf(\"[inline-reflection] error feedback: %d used learnings → fail_count++\", len(confirmedUsed))\n\t\t\t}\n\t\t}\n\n\t\t// Signal 4: Knowledge Gaps\n\t\tfor _, topic := range signals.GapTopics {\n\t\t\tgo s.queryDaemon(\"track_gap\", map[string]any{\"topic\": topic, \"project\": proj})\n\t\t}\n\n\t\t// Signal 3: Contradictions\n\t\tfor _, desc := range signals.Contradictions {\n\t\t\tgo s.queryDaemon(\"flag_contradiction\", map[string]any{\"description\": desc, \"project\": proj, \"thread_id\": threadID})\n\t\t}\n\n\t\tif len(confirmedUsed) > 0 || len(signals.GapTopics) > 0 || len(signals.Contradictions) > 0 {\n\t\t\ts.logger.Printf(\"[req %d %s tid=%s] inline-reflection: injected=%d used=%d noise=%d gaps=%d contradictions=%d\",\n\t\t\t\treqIdx, proj, threadID, len(prevInjected), len(confirmedUsed), len(noiseIDs), len(signals.GapTopics), len(signals.Contradictions))\n\t\t}\n\t}\n\n\t// Commit detection: check for git commit in tool_result, trigger async stale-learning evaluation\n\tif ci := detectGitCommit(messages); ci != nil {\n\t\tworkdir := extractWorkingDirectory(req)\n\t\ts.logger.Printf(\"[req %d %s tid=%s] git commit detected: %s %s\", reqIdx, proj, threadID, ci.Hash, ci.Message)\n\t\tgo s.queryDaemon(\"invalidate_on_commit\", map[string]any{\n\t\t\t\"hash\": ci.Hash, \"project\": proj, \"workdir\": workdir,\n\t\t})\n\t}\n\n\t// Loop detection: check for repeating tool-call patterns and stash warning.\n\t// The warning is merged into the last user message's TimestampMeta during\n\t// entry assembly below — cache-safe via freeze-once-replay through TimestampStore.\n\tloopWarning := \"\"\n\tif threadID != \"\" && !isRetryReq && isFeatureEnabled(&s.cfg, model, \"loop_warning\") {\n\t\ts.loopMu.Lock()\n\t\tloopState := s.loopStates[threadID]\n\t\tif loopState == nil {\n\t\t\tloopState = &LoopState{}\n\t\t\ts.loopStates[threadID] = loopState\n\t\t}\n\t\ts.loopMu.Unlock()\n\n\t\tif warning, level := CheckLoopAndFormat(messages, loopState); warning != \"\" {\n\t\t\tloopWarning = warning\n\t\t\ts.logger.Printf(\"%s[req %d %s tid=%s] LOOP: level %d warning stashed (cached via TimestampMeta)%s\", colorOrange, reqIdx, proj, threadID, level, colorReset)\n\t\t}\n\t}\n\n\t// Task #4: Subagent detection — skip all proxy logic for short conversations\n\tif isSubagent(messages, req) {\n\t\t// Inject docs hint for subagents if reference docs are indexed\n\t\tif hint := s.getDocsHint(proj); hint != \"\" {\n\t\t\tmodified := injectDocsHintForSubagent(messages, hint)\n\t\t\treq[\"messages\"] = modified\n\t\t\tif newBody, err := json.Marshal(req); err == nil {\n\t\t\t\tbody = newBody\n\t\t\t}\n\t\t\ts.logger.Printf(\"%s[req %d %s tid=%s] %d messages (subagent, docs-hint injected)%s\", colorOrange, reqIdx, proj, threadID, len(messages), colorReset)\n\t\t} else {\n\t\t\ts.logger.Printf(\"%s[req %d %s tid=%s] %d messages (subagent, passthrough)%s\", colorOrange, reqIdx, proj, threadID, len(messages), colorReset)\n\t\t}\n\t\ts.forwardWithAnnotation(w, r, body, reqIdx, toolUseIDs, proj, threadID, msgCount)\n\t\treturn\n\t}\n\n\t// === Sawtooth Cache Optimization ===\n\t// When enabled: use frozen stubs between stub-cycles for cache hits.\n\t// When disabled: fall back to progressive decay on every request above threshold.\n\n\tsawtoothCutoff := 0 // raw index where fresh tail starts (0 = no frozen stubs)\n\tsawtoothStubs := 0 // number of frozen stubs in combined array\n\n\tif s.cfg.SawtoothEnabled {\n\t\t// Restore decay state from DB on cold start (before frozen stubs use it)\n\t\ts.decay.LoadFromDB(threadID)\n\n\t\t// Check for existing frozen stubs first\n\t\tfrozen := s.frozenStubs.Get(threadID, messages)\n\t\tif frozen != nil {\n\t\t\tsawtoothCutoff = frozen.Cutoff\n\t\t\tsawtoothStubs = len(frozen.Messages)\n\t\t\t// Estimate combined token count: frozen prefix + fresh tail + overhead\n\t\t\tfreshMessages := messages[frozen.Cutoff:]\n\t\t\tfreshTokens := s.countMessageTokens(freshMessages)\n\t\t\tcombinedTokens := frozen.Tokens + freshTokens + overhead\n\n\t\t\tif shouldInvalidateFrozen(totalTokens, s.effectiveTokenThreshold(model)) {\n\t\t\t\t// Fresh tail grew too large — invalidate and re-stub\n\t\t\t\ts.logger.Printf(\"[req %d %s tid=%s] SAWTOOTH: frozen prefix expired (totalTokens=%dk > %dk threshold, combined=%dk: %dk frozen + %dk fresh + %dk overhead)\",\n\t\t\t\t\treqIdx, proj, threadID, totalTokens/1000, s.effectiveTokenThreshold(model)/1000, combinedTokens/1000, frozen.Tokens/1000, freshTokens/1000, overhead/1000)\n\t\t\t\ts.invalidateThreadCaches(threadID, proj, extractWorkingDirectory(req))\n\t\t\t\tfrozen = nil // fall through to trigger check below\n\t\t\t} else {\n\t\t\t\t// Use frozen prefix + fresh tail\n\t\t\t\tcombined := make([]any, 0, len(frozen.Messages)+len(freshMessages))\n\t\t\t\tcombined = append(combined, frozen.Messages...)\n\t\t\t\tcombined = append(combined, freshMessages...)\n\t\t\t\t// Eager-stub large tool_results in fresh tail (model already processed them)\n\t\t\t\tbeforeEager := s.countMessageTokens(combined[len(frozen.Messages):])\n\t\t\t\tvar stubSticky, stubFresh int\n\t\t\t\tcombined = EagerStubToolResults(combined, len(frozen.Messages), s.countTokens, WithStubMemory(s.eagerStubMemory, threadID), WithStubCounters(&stubSticky, &stubFresh))\n\t\t\t\tafterEager := s.countMessageTokens(combined[len(frozen.Messages):])\n\t\t\t\tif beforeEager != afterEager {\n\t\t\t\t\ts.logger.Printf(\"[req %d %s tid=%s] EAGER-STUB: fresh %dk → %dk (saved %dk) [sticky=%d fresh=%d]\", reqIdx, proj, threadID, beforeEager/1000, afterEager/1000, (beforeEager-afterEager)/1000, stubSticky, stubFresh)\n\t\t\t\t}\n\t\t\t\treq[\"messages\"] = combined\n\t\t\t\tneedsReserialization = true\n\t\t\t\t// Strip embedded CC breakpoints from frozen stubs — they waste cache slots.\n\t\t\t\t// InjectFrozenStubCacheBreakpoint adds exactly one below.\n\t\t\t\tif n := StripMessagesCacheControl(req, 0, len(frozen.Messages)); n > 0 {\n\t\t\t\t\ts.logger.Printf(\"[req %d %s tid=%s] SAWTOOTH: stripped %d embedded breakpoints from frozen stubs\", reqIdx, proj, threadID, n)\n\t\t\t\t}\n\t\t\t\ts.logger.Printf(\"%s[req %d %s tid=%s] SAWTOOTH: frozen prefix (%d stubs, %dk msg) + %d fresh (%dk msg) + %dk overhead = %dk total%s\",\n\t\t\t\t\tcolorGreen, reqIdx, proj, threadID, len(frozen.Messages), frozen.Tokens/1000, len(freshMessages), freshTokens/1000, overhead/1000, combinedTokens/1000, colorReset)\n\t\t\t\trawMsgTokens := s.countMessageTokens(messages)\n\t\t\t\ts.setRawEstimate(threadID, rawMsgTokens+overhead)\n\t\t\t\ts.logger.Printf(\"[req %d %s tid=%s] RAW ESTIMATE: msgTokens=%dk + overhead=%dk = %dk (messages=%d)\", reqIdx, proj, threadID, rawMsgTokens/1000, overhead/1000, (rawMsgTokens+overhead)/1000, len(messages))\n\t\t\t\tif InjectFrozenStubCacheBreakpoint(req, len(frozen.Messages)) {\n\t\t\t\t\ts.logger.Printf(\"[req %d %s tid=%s] SAWTOOTH: frozen stub cache breakpoint at messages[%d]\",\n\t\t\t\t\t\treqIdx, proj, threadID, len(frozen.Messages)-1)\n\t\t\t\t}\n\n\t\t\t\t// Ensure frozen stubs are persisted (may have failed on first request after deploy)\n\t\t\t\ts.ensureFrozenPersisted(threadID)\n\t\t\t}\n\t\t}\n\t\tif frozen == nil {\n\t\t\t// No frozen stubs — check trigger\n\t\t\ttriggerReason := s.sawtoothTrigger.ShouldTrigger(threadID, totalTokens)\n\t\t\tif triggerReason != TriggerNone {\n\t\t\t\ts.logger.Printf(\"[req %d %s tid=%s] SAWTOOTH stub-cycle triggered: %s (tokens=%dk)\",\n\t\t\t\t\treqIdx, proj, threadID, triggerReason, totalTokens/1000)\n\n\t\t\t\t// Refresh briefing during stub-cycle — disabled (briefing now via SessionStart hook)\n\t\t\t\t// s.refreshBriefing(proj)\n\t\t\t\t// if s.briefingText != \"\" {\n\t\t\t\t// \tUpsertSystemBlockCached(req, \"yesmem-briefing\", s.briefingText)\n\t\t\t\t// }\n\n\t\t\t\t// Run full stub pipeline\n\t\t\t\ts.setRawEstimate(threadID, totalTokens)\n\t\t\t\t_ = s.runStubCycle(messages, req, reqIdx, proj, threadID, overhead, totalTokens, userQuery, isRetryReq)\n\n\t\t\t\t// Freeze the result\n\t\t\t\tfinalMessages, _ := req[\"messages\"].([]any)\n\t\t\t\tif finalMessages != nil && len(finalMessages) > 0 {\n\t\t\t\t\t// Boundary = last message of original that maps to last stub\n\t\t\t\t\tcutoff := len(messages)\n\t\t\t\t\tsawtoothCutoff = cutoff\n\t\t\t\t\tvar boundaryMsg any\n\t\t\t\t\tif cutoff > 0 {\n\t\t\t\t\t\tboundaryMsg = messages[cutoff-1]\n\t\t\t\t\t}\n\t\t\t\t\tfrozenTokens := s.countMessageTokens(finalMessages)\n\t\t\t\t\tfrozenCount, stripped, breakpointInjected := s.freezeStubsAndInjectBreakpoint(req, threadID, cutoff, boundaryMsg, frozenTokens, totalTokens)\n\t\t\t\t\ts.decay.Persist(threadID)\n\t\t\t\t\tif stripped > 0 {\n\t\t\t\t\t\ts.logger.Printf(\"[req %d %s tid=%s] SAWTOOTH: stripped %d embedded breakpoints before freeze\",\n\t\t\t\t\t\t\treqIdx, proj, threadID, stripped)\n\t\t\t\t\t}\n\t\t\t\t\ts.logger.Printf(\"[req %d %s tid=%s] SAWTOOTH: frozen %d messages at cutoff=%d (~%dk tokens)\",\n\t\t\t\t\t\treqIdx, proj, threadID, frozenCount, cutoff, frozenTokens/1000)\n\t\t\t\t\tif breakpointInjected {\n\t\t\t\t\t\ts.logger.Printf(\"[req %d %s tid=%s] SAWTOOTH: frozen stub cache breakpoint at messages[%d]\",\n\t\t\t\t\t\t\treqIdx, proj, threadID, frozenCount-1)\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tneedsReserialization = true\n\t\t\t} else {\n\t\t\t\t// No trigger, no frozen stubs — eager-stub to delay first collapse\n\t\t\t\tbeforeEager := s.countMessageTokens(messages)\n\t\t\t\tvar stubSticky, stubFresh int\n\t\t\t\tmessages = EagerStubToolResults(messages, 0, s.countTokens, WithStubMemory(s.eagerStubMemory, threadID), WithStubCounters(&stubSticky, &stubFresh))\n\t\t\t\tafterEager := s.countMessageTokens(messages)\n\t\t\t\tif beforeEager != afterEager {\n\t\t\t\t\ts.logger.Printf(\"[req %d %s tid=%s] EAGER-STUB: %dk → %dk (saved %dk) [sticky=%d fresh=%d]\", reqIdx, proj, threadID, beforeEager/1000, afterEager/1000, (beforeEager-afterEager)/1000, stubSticky, stubFresh)\n\t\t\t\t\treq[\"messages\"] = messages\n\t\t\t\t\tneedsReserialization = true\n\t\t\t\t}\n\t\t\t\ts.logger.Printf(\"%s[req %d %s tid=%s] %d messages, ~%dk tokens (sawtooth: no trigger)%s\",\n\t\t\t\t\tcolorDim, reqIdx, proj, threadID, len(messages), totalTokens/1000, colorReset)\n\t\t\t}\n\t\t}\n\t} else {\n\t\t// Legacy path: progressive decay\n\t\tif s.shouldStub(totalTokens, model) {\n\t\t\ts.setRawEstimate(threadID, totalTokens)\n\t\t\t_ = s.runStubCycle(messages, req, reqIdx, proj, threadID, overhead, totalTokens, userQuery, isRetryReq)\n\t\t\tneedsReserialization = true\n\t\t}\n\t}\n\n\t// Persistent timestamp injection: re-injects stored timestamps on ALL previous user messages.\n\t// msg:N is computed from position (counting user messages in raw array), NOT from a counter.\n\t// The injected data is deterministic (same bytes every turn) = cache-safe after initial miss.\n\tif threadID != \"\" {\n\t\tif currentMsgs, ok := req[\"messages\"].([]any); ok && len(currentMsgs) > 0 {\n\t\t\ts.timestampStore.Load(threadID)\n\n\t\t\t// Offset = cutoff - stubsCount: maps combined array index to raw position.\n\t\t\t// Raw position of message at combined index I = cutoff + (I - stubs).\n\t\t\t// msgN = rawPos + 1 = cutoff + (I - stubs) + 1 = (cutoff - stubs) + I + 1.\n\t\t\tmsgOffset := sawtoothCutoff - sawtoothStubs\n\n\t\t\ts.logger.Printf(\"[req %d %s] timestamps: rawTotal=%d, postSawtooth=%d, cutoff=%d, stubs=%d, offset=%d\", reqIdx, proj, rawMsgCount, len(currentMsgs), sawtoothCutoff, sawtoothStubs, msgOffset)\n\n\t\t\t// Pre-store assistant timestamp BEFORE inject loop so it's available immediately\n\t\t\ts.responseTsMu.RLock()\n\t\t\trespTime, hasRespTime := s.responseTimes[threadID]\n\t\t\ts.responseTsMu.RUnlock()\n\t\t\tif hasRespTime {\n\t\t\t\trespStr := shortWeekday(respTime.Weekday()) + \" \" + respTime.Format(\"2006-01-02 15:04:05\")\n\t\t\t\tfor j := len(currentMsgs) - 2; j >= 0; j-- {\n\t\t\t\t\tprevMsg, _ := currentMsgs[j].(map[string]any)\n\t\t\t\t\tif prevMsg == nil || prevMsg[\"role\"] != \"assistant\" {\n\t\t\t\t\t\tcontinue\n\t\t\t\t\t}\n\t\t\t\t\tif !msgHasTextBlock(prevMsg) {\n\t\t\t\t\t\ts.logger.Printf(\"[req %d %s] timestamps: skip assistant[%d] no text block\", reqIdx, proj, j)\n\t\t\t\t\t\tcontinue // skip tool-use-only assistant messages\n\t\t\t\t\t}\n\t\t\t\t\tprevMsgN := msgOffset + j + 1\n\t\t\t\t\tif _, exists := s.timestampStore.Get(threadID, prevMsgN); !exists {\n\t\t\t\t\t\ts.timestampStore.Store(threadID, prevMsgN, &TimestampMeta{Timestamp: respStr})\n\t\t\t\t\t\ts.logger.Printf(\"[req %d %s] timestamps: stored assistant msg:%d ts=%s\", reqIdx, proj, prevMsgN, respStr)\n\t\t\t\t\t} else {\n\t\t\t\t\t\ts.logger.Printf(\"[req %d %s] timestamps: assistant msg:%d already has ts\", reqIdx, proj, prevMsgN)\n\t\t\t\t\t}\n\t\t\t\t\tbreak\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\ts.logger.Printf(\"[req %d %s] timestamps: no responseTimes for thread\", reqIdx, proj)\n\t\t\t}\n\n\t\t\t// Inject [msg:N] (+ timestamp/delta if stored) on all PREVIOUS messages\n\t\t\tif isFeatureEnabled(&s.cfg, model, \"timestamps\") {\n\t\t\t\tif n := InjectTimestamps(s.timestampStore, threadID, currentMsgs, len(currentMsgs)-1, msgOffset, sawtoothStubs); n > 0 {\n\t\t\t\t\tneedsReserialization = true\n\t\t\t\t\ts.logger.Printf(\"[req %d %s] timestamps: injected %d\", reqIdx, proj, n)\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t// Current (last) user message: full timestamp + delta + msg:N from raw count\n\t\t\t// Loop warning (if stashed above) is merged here — cache-safe via freeze-once-replay.\n\t\t\tlastMsg, _ := currentMsgs[len(currentMsgs)-1].(map[string]any)\n\t\t\tif lastMsg != nil && (lastMsg[\"role\"] == \"user\" || lastMsg[\"role\"] == \"tool\") && !msgHasMetaPrefix(lastMsg) {\n\t\t\t\tnow := time.Now()\n\t\t\t\tnowStr := shortWeekday(now.Weekday()) + \" \" + now.Format(\"2006-01-02 15:04:05\")\n\t\t\t\tentry := &TimestampMeta{Timestamp: nowStr}\n\t\t\t\tif hasRespTime {\n\t\t\t\t\tentry.Delta = formatDelta(now.Sub(respTime))\n\t\t\t\t}\n\t\t\t\tentry.LoopWarning = loopWarning\n\n\t\t\t\t// Plan checkpoint: detect plan tool calls (resets counter), inject reminder if interval fires\n\t\t\t\tif isFeatureEnabled(&s.cfg, model, \"plan_checkpoint\") {\n\t\t\t\t\ts.detectPlanToolCall(messages, threadID, totalTokens)\n\t\t\t\t\tif checkpoint := s.planCheckpointInject(threadID, totalTokens); checkpoint != \"\" {\n\t\t\t\t\t\tentry.PlanCheckpoint = checkpoint\n\t\t\t\t\t\ts.logger.Printf(\"%s[req %d %s tid=%s] plan checkpoint stashed (cached via TimestampMeta)%s\", colorBlue, reqIdx, proj, threadID, colorReset)\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\t// Docs hint: inject docs-available reminder if interval fires\n\t\t\t\tif docsHint := s.docsHintInject(threadID, totalTokens, proj); docsHint != \"\" {\n\t\t\t\t\tentry.DocsHint = docsHint\n\t\t\t\t\ts.logger.Printf(\"%s[req %d %s tid=%s] docs hint stashed (cached via TimestampMeta)%s\", colorBlue, reqIdx, proj, threadID, colorReset)\n\t\t\t\t}\n\n\t\t\t\ts.timestampStore.Store(threadID, rawMsgCount, entry)\n\t\t\t\tif isFeatureEnabled(&s.cfg, model, \"timestamps\") || entry.LoopWarning != \"\" || entry.PlanCheckpoint != \"\" || entry.DocsHint != \"\" {\n\t\t\t\t\tprependMeta(lastMsg, BuildMeta(rawMsgCount, entry))\n\t\t\t\t}\n\t\t\t\tneedsReserialization = true\n\t\t\t}\n\n\t\t\ts.timestampStore.Persist(threadID)\n\t\t}\n\t}\n\n\t// Associative context injection (for ALL requests, not just stubbed ones)\n\t// Re-enabled with higher thresholds (55/75, max 1) after disabling Fresh Remember.\n\tassocContext := s.findAssociativeContextFor(userQuery, proj, threadID, messages)\n\tif assocContext != \"\" {\n\t\tcurrentMessages, _ := req[\"messages\"].([]any)\n\t\tif currentMessages == nil {\n\t\t\tcurrentMessages = messages\n\t\t}\n\t\treq[\"messages\"] = injectAssociativeContext(currentMessages, assocContext, s.cfg.SawtoothEnabled)\n\t\tneedsReserialization = true\n\t\ts.logger.Printf(\"%s[req %d %s tid=%s] associative context injected%s\", colorBlue, reqIdx, proj, threadID, colorReset)\n\t}\n\n\t// Doc-chunk context injection: separate search path for indexed docs (not skills)\n\tdocContext := s.findDocContextFor(userQuery, proj, messages)\n\tif docContext != \"\" {\n\t\tcurrentMessages, _ := req[\"messages\"].([]any)\n\t\tif currentMessages == nil {\n\t\t\tcurrentMessages = messages\n\t\t}\n\t\treq[\"messages\"] = injectAssociativeContext(currentMessages, docContext, s.cfg.SawtoothEnabled)\n\t\tneedsReserialization = true\n\t\ts.logger.Printf(\"%s[req %d %s tid=%s] doc context injected%s\", colorBlue, reqIdx, proj, threadID, colorReset)\n\t}\n\n\t// Fresh Remember Injection: DISABLED\n\t// Causes echo-loop: Claude saves via remember(), sees it again next turn as\n\t// [yesmem fresh memory], reacts to own output instead of user's request.\n\t// Recovery after /clear is handled by recovery.go (briefing), not here.\n\t// Subagents get their own briefing. No remaining use-case.\n\t// remItems := s.popRecentRemember()\n\tvar remItems []recentLearningItem\n\n\t// Rules re-injection: inject condensed CLAUDE.md rules every ~40k output tokens\n\tif rulesBlock := s.rulesInject(threadID, totalTokens, proj); rulesBlock != \"\" {\n\t\tcurrentMessages, _ := req[\"messages\"].([]any)\n\t\tif currentMessages == nil {\n\t\t\tcurrentMessages = messages\n\t\t}\n\t\treq[\"messages\"] = injectAssociativeContext(currentMessages, s.formatRulesReminder(rulesBlock, proj, false), s.cfg.SawtoothEnabled)\n\t\tneedsReserialization = true\n\t\ts.logger.Printf(\"%s[req %d %s tid=%s] rules reminder injected%s\", colorBlue, reqIdx, proj, threadID, colorReset)\n\t}\n\n\t// Plan auto-activation: nudge Claude to call set_plan() when reading a plan file\n\tif planFile := detectPlanFileRead(messages); planFile != \"\" {\n\t\tif activePlan, _ := s.getActivePlan(threadID); shouldNudgePlan(planFile, activePlan) {\n\t\t\tAppendSystemBlock(req, \"plan-nudge\",\n\t\t\t\tfmt.Sprintf(\"Du hast einen Implementierungsplan gelesen (%s). Aktiviere ihn jetzt via set_plan() damit Plan-Checkpoints funktionieren und dein Fortschritt getrackt wird.\", filepath.Base(planFile)))\n\t\t\tneedsReserialization = true\n\t\t\ts.logger.Printf(\"%s[req %d %s tid=%s] plan nudge injected for %s%s\", colorBlue, reqIdx, proj, threadID, filepath.Base(planFile), colorReset)\n\t\t}\n\t}\n\n\t// Plan checkpoint and docs hint are now injected via TimestampMeta freeze-once\n\t// in the current-message annotation block above. The old injectAssociativeContext\n\t// calls were removed as part of the cache-safe migration (see merge 58bca90a).\n\n\tif len(remItems) > 0 {\n\t\tvar lines []string\n\t\tfor _, item := range remItems {\n\t\t\tif item.ID > 0 {\n\t\t\t\tlines = append(lines, fmt.Sprintf(\"- [ID:%d] %s\", item.ID, truncateStr(item.Text, 200)))\n\t\t\t} else {\n\t\t\t\tlines = append(lines, \"- \"+truncateStr(item.Text, 200))\n\t\t\t}\n\t\t}\n\t\tremContext := fmt.Sprintf(\"[yesmem fresh memory]\\nGerade gelernt:\\n%s\\n%s\\n[/yesmem fresh memory]\", strings.Join(lines, \"\\n\"), InlineReflectionHint)\n\t\tcurrentMessages, _ := req[\"messages\"].([]any)\n\t\tif currentMessages == nil {\n\t\t\tcurrentMessages = messages\n\t\t}\n\t\treq[\"messages\"] = injectAssociativeContext(currentMessages, remContext, s.cfg.SawtoothEnabled)\n\t\tneedsReserialization = true\n\t\ts.logger.Printf(\"%s[req %d %s tid=%s] fresh remember injected (%d items)%s\", colorBlue, reqIdx, proj, threadID, len(remItems), colorReset)\n\t}\n\n\t// Skill eval injection: mandatory evaluation instruction for real user input only\n\tif threadID != \"\" && proj != \"\" && isUserInputTurn(messages) {\n\t\t// First: detect any skill activations from message history (updates tracker)\n\t\ts.syncSkillActivations(messages, proj, threadID)\n\n\t\t// Inject mandatory skill evaluation block into last user message\n\t\tskillEval := buildSkillEvalBlock(s.cfg.SkillEvalInject)\n\t\tcurrentMessages, _ := req[\"messages\"].([]any)\n\t\tif currentMessages == nil {\n\t\t\tcurrentMessages = messages\n\t\t}\n\t\tif skillEval != \"\" {\n\t\t\treq[\"messages\"] = injectAssociativeContext(currentMessages, skillEval, s.cfg.SawtoothEnabled)\n\t\t\tneedsReserialization = true\n\t\t\ts.logger.Printf(\"%s[req %d %s tid=%s] skill-eval injected (mode=%s)%s\", colorBlue, reqIdx, proj, threadID, s.cfg.SkillEvalInject, colorReset)\n\t\t}\n\t}\n\n\t// === Collect injected learning IDs for next turn's inline reflection ===\n\tif threadID != \"\" {\n\t\tcurrentIDs := make(map[int64]string) // id → source\n\t\t// Source 1: Briefing learnings (cached, same every turn)\n\t\tbriefingSnapshot, _, _ := s.getCachedBriefing(threadID, proj)\n\t\tif briefingSnapshot != \"\" {\n\t\t\tfor _, m := range idPattern.FindAllStringSubmatch(briefingSnapshot, -1) {\n\t\t\t\tif id, err := strconv.ParseInt(m[1], 10, 64); err == nil {\n\t\t\t\t\tcurrentIDs[id] = \"briefing\"\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\t// Source 2: Associative context (contextual, per-turn)\n\t\tif assocContext != \"\" {\n\t\t\tfor _, m := range idPattern.FindAllStringSubmatch(assocContext, -1) {\n\t\t\t\tif id, err := strconv.ParseInt(m[1], 10, 64); err == nil {\n\t\t\t\t\tcurrentIDs[id] = \"associative\" // overwrites briefing if both — associative is the stronger signal\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\t// Source 3: Fresh remember items (just learned)\n\t\tfor _, item := range remItems {\n\t\t\tif item.ID > 0 {\n\t\t\t\tcurrentIDs[item.ID] = \"fresh\"\n\t\t\t}\n\t\t}\n\t\ts.lastInjectedIDsMu.Lock()\n\t\tif len(currentIDs) > 0 {\n\t\t\t// Accumulate IDs across turns — don't overwrite.\n\t\t\t// Claude may reference an ID from turn 30 in turn 50.\n\t\t\texisting := s.lastInjectedIDs[threadID]\n\t\t\tif existing == nil {\n\t\t\t\texisting = make(map[int64]string)\n\t\t\t}\n\t\t\tfor id, source := range currentIDs {\n\t\t\t\texisting[id] = source\n\t\t\t}\n\t\t\ts.lastInjectedIDs[threadID] = existing\n\t\t}\n\t\ts.lastInjectedIDsMu.Unlock()\n\t}\n\n\t// Think reminder injection — only for real user turns, not tool continuations\n\tif threadID != \"\" {\n\t\tcurrentMessages, _ := req[\"messages\"].([]any)\n\t\tif currentMessages == nil {\n\t\t\tcurrentMessages = messages\n\t\t}\n\t\tif lastUserHasText(currentMessages) && lastUserTextLen(currentMessages) >= resolveThinkReminderMinChars(&s.cfg, \"\") {\n\t\t\tthinkReminder := s.buildThinkReminder(threadID, threadID, false)\n\t\t\tif thinkReminder != \"\" {\n\t\t\t\treq[\"messages\"] = injectAssociativeContext(currentMessages, thinkReminder, s.cfg.SawtoothEnabled)\n\t\t\t\tneedsReserialization = true\n\t\t\t}\n\t\t}\n\n\t\t// Refresh frozen-prefix snapshot with the post-timestamps bytes that\n\t\t// will appear in the wire body for the frozen range. This MUST run\n\t\t// BEFORE injectBriefingTurn / injectCodeMapTurn / injectCapabilitiesTurn:\n\t\t// those stages prepend or insert turns into req[\"messages\"], which\n\t\t// shifts the frozen range to a higher offset. Slicing\n\t\t// req[\"messages\"][:frozenLen] AFTER inject would capture the injected\n\t\t// turns at the head and corrupt the stored frozen prefix, breaking\n\t\t// cache continuity for 5-9 turns after every emergency-sawtooth.\n\t\tif frozenLen := s.frozenStubs.LengthFor(threadID); frozenLen > 0 {\n\t\t\tif msgs, ok := req[\"messages\"].([]any); ok && len(msgs) >= frozenLen {\n\t\t\t\tif s.frozenStubs.UpdateMessages(threadID, msgs[:frozenLen]) {\n\t\t\t\t\ts.logger.Printf(\"[req %d %s tid=%s] FROZEN-SNAPSHOT: refreshed %d pre-inject messages\", reqIdx, proj, threadID, frozenLen)\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\t// Briefing injection: prepend user/assistant turn pair at beginning of messages.\n\t\t// Static per session → stable prefix → cacheable. Must be before dialog injection.\n\t\tif s.injectBriefingTurn(req, reqIdx, proj, threadID) {\n\t\t\tneedsReserialization = true\n\t\t}\n\n\t\t// Code Map injection: insert after briefing turn pair.\n\t\tif s.injectCodeMapTurn(req, reqIdx, proj, threadID) {\n\t\t\tneedsReserialization = true\n\t\t}\n\n\t\t// Active capabilities injection: insert user/assistant pair directly\n\t\t// before the last user message. Fresh per turn (no cache) so\n\t\t// activate/deactivate takes effect immediately. Must run AFTER\n\t\t// briefing/codeMap (both prepend at beginning) and BEFORE dialog\n\t\t// injection (which appends at end) to preserve cache for the\n\t\t// briefing prefix and keep the injection close to the real user\n\t\t// turn.\n\t\tif s.injectCapabilitiesTurn(req, threadID) {\n\t\t\tneedsReserialization = true\n\t\t}\n\n\t\t// Dialog injection: append at end of messages with pseudo-padding for alternation.\n\t\t// Always works regardless of tool_result chains or conversation state.\n\t\tdr := s.checkDialogMessages(threadID, proj)\n\t\tif dr.Extra != \"\" {\n\t\t\t// Prepend session ID for agent-to-agent communication\n\t\t\tdr.Extra = fmt.Sprintf(\"DEINE_SESSION_ID: %s\\n\", threadID) + dr.Extra\n\t\t\tinjected := false\n\t\t\tif msgs, ok := req[\"messages\"].([]any); ok && len(msgs) > 0 {\n\t\t\t\t// Pad with empty assistant if last message is user (maintains alternation)\n\t\t\t\tlastMsg, _ := msgs[len(msgs)-1].(map[string]any)\n\t\t\t\tif lastMsg != nil && lastMsg[\"role\"] == \"user\" {\n\t\t\t\t\tmsgs = append(msgs, map[string]any{\n\t\t\t\t\t\t\"role\": \"assistant\",\n\t\t\t\t\t\t\"content\": \"​\", // zero-width space — invisible padding\n\t\t\t\t\t})\n\t\t\t\t}\n\t\t\t\t// Append dialog as new user message (becomes the last message Claude sees)\n\t\t\t\tmsgs = append(msgs, map[string]any{\n\t\t\t\t\t\"role\": \"user\",\n\t\t\t\t\t\"content\": \"\\n\" + dr.Extra + \"\\n\",\n\t\t\t\t})\n\t\t\t\treq[\"messages\"] = msgs\n\t\t\t\tneedsReserialization = true\n\t\t\t\tinjected = true\n\t\t\t}\n\t\t\t// Mark as read after 3 injection turns — long enough for Claude to process,\n\t\t\t// resilient against fast ping-pong sessions (time-based was broken for <30s turns).\n\t\t\tif injected && dr.HasUnread && dr.SessionID != \"\" {\n\t\t\t\tif s.shouldMarkChannelRead(dr) {\n\t\t\t\t\ts.markDialogRead(dr)\n\t\t\t\t}\n\t\t\t}\n\t\t\ts.logger.Printf(\"[dialog] injection injected=%v extra_len=%d\", injected, len(dr.Extra))\n\t\t}\n\t}\n\n\t// Signal tools are NOT injected into the main request.\n\t// They are collected via a separate async reflection call after the response.\n\t// See fireReflectionCall() in reflection.go.\n\t// NEVER put _signal_* tools back here — the model ignores them among 44+ real tools.\n\n\t// Legacy assistant annotation: timestamp on last assistant message after cache breakpoint.\n\t// User timestamps are now handled by TimestampStore (injected earlier in the pipeline).\n\tif threadID != \"\" {\n\t\tif msgs, ok := req[\"messages\"].([]any); ok && len(msgs) > 0 {\n\t\t\t// Find last cache_control breakpoint in messages\n\t\t\tcacheIdx := -1\n\t\t\tfor i, m := range msgs {\n\t\t\t\tmsg, _ := m.(map[string]any)\n\t\t\t\tif msg == nil {\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\t\t\t\tif _, has := msg[\"cache_control\"]; has {\n\t\t\t\t\tcacheIdx = i\n\t\t\t\t}\n\t\t\t\tif content, ok := msg[\"content\"].([]any); ok {\n\t\t\t\t\tfor _, block := range content {\n\t\t\t\t\t\tif b, ok := block.(map[string]any); ok {\n\t\t\t\t\t\t\tif _, has := b[\"cache_control\"]; has {\n\t\t\t\t\t\t\t\tcacheIdx = i\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t\tstartIdx := cacheIdx + 1\n\t\t\tif startIdx < 0 {\n\t\t\t\tstartIdx = 0\n\t\t\t}\n\t\t\ts.responseTsMu.RLock()\n\t\t\trespTime, hasRespTime := s.responseTimes[threadID]\n\t\t\ts.responseTsMu.RUnlock()\n\t\t\tif hasRespTime {\n\t\t\t\tfor i := len(msgs) - 1; i >= startIdx; i-- {\n\t\t\t\t\tmsg, _ := msgs[i].(map[string]any)\n\t\t\t\t\tif msg != nil && msg[\"role\"] == \"assistant\" && msgHasTextBlock(msg) {\n\t\t\t\t\t\tmeta := fmt.Sprintf(\"[%s] [msg:%d]\", respTime.Format(\"2006-01-02 15:04:05\"), i)\n\t\t\t\t\t\tprependMeta(msg, meta)\n\t\t\t\t\t\tneedsReserialization = true\n\t\t\t\t\t\tbreak\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\t// Self-Priming: DISABLED (experimental, see signal_handlers.go comment block).\n\t// The reflection-based selfprime (Haiku) was too inaccurate to be useful —\n\t// the main model adapts to user context organically from message history.\n\t// Kept for reference; re-enable by uncommenting below + setting ShouldActivate=true.\n\t//\n\t// s.selfPrimeMu.RLock()\n\t// anchor := s.selfPrimes[threadID]\n\t// s.selfPrimeMu.RUnlock()\n\t// if anchor != \"\" {\n\t// \tif msgs, ok := req[\"messages\"].([]any); ok && len(msgs) > 0 {\n\t// \t\tlastMsg, _ := msgs[len(msgs)-1].(map[string]any)\n\t// \t\tif lastMsg != nil && lastMsg[\"role\"] == \"user\" {\n\t// \t\t\tprimeBlock := map[string]any{\n\t// \t\t\t\t\"type\": \"text\",\n\t// \t\t\t\t\"text\": \"[self-prime]\\n[Self-Prime von deiner letzten Antwort]: \" + anchor,\n\t// \t\t\t}\n\t// \t\t\tif content, ok := lastMsg[\"content\"].([]any); ok {\n\t// \t\t\t\tlastMsg[\"content\"] = append(content, primeBlock)\n\t// \t\t\t} else if text, ok := lastMsg[\"content\"].(string); ok {\n\t// \t\t\t\tlastMsg[\"content\"] = []any{\n\t// \t\t\t\t\tmap[string]any{\"type\": \"text\", \"text\": text},\n\t// \t\t\t\t\tprimeBlock,\n\t// \t\t\t\t}\n\t// \t\t\t}\n\t// \t\t\tneedsReserialization = true\n\t// \t\t}\n\t// \t}\n\t// }\n\n\t// Prompt Caching: inject cache_control breakpoints on system + tools\n\t// Sawtooth: disabled (all 4 slots are used by CC system (2) + briefing (1) + CC messages (1))\n\t// Legacy: count existing breakpoints and fill up remaining slots.\n\tif !s.cfg.SawtoothEnabled && s.cacheGate.ShouldCache() {\n\t\tif n := InjectCacheBreakpoints(req, s.logger); n > 0 {\n\t\t\tneedsReserialization = true\n\t\t\ts.logger.Printf(\"[req %d %s] prompt cache: %d breakpoints injected\", reqIdx, proj, n)\n\t\t} else {\n\t\t\ts.logger.Printf(\"[req %d %s] prompt cache: no budget (4/4 already set)\", reqIdx, proj)\n\t\t}\n\t}\n\n\t// Final: upgrade cache TTL + enforce breakpoint limit (must run AFTER all injections + InjectCacheBreakpoints)\n\t// Shift the last messages-breakpoint from unstable user message to stable assistant message.\n\t// Text user messages change between turns (timestamps, reminders, structure); assistant messages don't.\n\t// Tool-result messages are excluded — they are stable cache anchors.\n\tif ShiftMessageBreakpoint(req) {\n\t\tneedsReserialization = true\n\t}\n\t// Normalize cache_control TTL across all blocks to prevent Anthropic from\n\t// rejecting requests with increasing TTLs in block order. Auto-detects 1h\n\t// when any existing block already carries it (config, Claude Code\n\t// passthrough on --resume, or stale frozen prefix from an earlier turn).\n\tif n := NormalizeCacheTTL(req, s.cfg.CacheTTL); n > 0 {\n\t\tneedsReserialization = true\n\t\ts.logger.Printf(\"[req %d %s] cache TTL normalized: %d blocks\", reqIdx, proj, n)\n\t}\n\tif n := EnforceCacheBreakpointLimit(req, maxCacheBreakpoints); n > 0 {\n\t\tneedsReserialization = true\n\t\ts.logger.Printf(\"[req %d %s] prompt cache: trimmed %d surplus breakpoints\", reqIdx, proj, n)\n\t}\n\n\t// Pre-flight: validate tool_use/tool_result pairing before forwarding.\n\t// Repairs orphans created by Stubify/Collapse/Injection pipeline.\n\tif msgs, ok := req[\"messages\"].([]any); ok {\n\t\trepaired, orphans := validateToolPairs(msgs, s.logger)\n\t\tif orphans > 0 {\n\t\t\treq[\"messages\"] = repaired\n\t\t\ts.logger.Printf(\"%s[req %d %s tid=%s] validate: repaired %d tool pairing issue(s)%s\", colorOrange, reqIdx, proj, threadID, orphans, colorReset)\n\t\t\tneedsReserialization = true\n\t\t}\n\t}\n\n\t// Early-out: skip re-serialization when nothing changed\n\tif !needsReserialization {\n\t\ts.logger.Printf(\"%s[req %d %s tid=%s] %d messages, ~%dk tokens (no stubbing, threshold=%dk)%s\", colorDim, reqIdx, proj, threadID, len(messages), totalTokens/1000, s.effectiveTokenThreshold(model)/1000, colorReset)\n\t\ts.forwardWithAnnotation(w, r, body, reqIdx, toolUseIDs, proj, threadID, msgCount, totalTokens)\n\t\treturn\n\t}\n\n\t// Re-serialize\n\tbody, err = json.Marshal(req)\n\tif err != nil {\n\t\ts.logger.Printf(\"%s[req %d %s tid=%s] re-marshal error: %v%s\", colorRed, reqIdx, proj, threadID, err, colorReset)\n\t\thttp.Error(w, \"internal proxy error\", http.StatusInternalServerError)\n\t\treturn\n\t}\n\n\t// Debug: dump final request body to file (enable with YESMEM_PROXY_DEBUG=1)\n\tif needsReserialization && os.Getenv(\"YESMEM_PROXY_DEBUG\") == \"1\" {\n\t\tdumpDir := filepath.Join(s.cfg.DataDir, \"debug\")\n\t\tos.MkdirAll(dumpDir, 0755)\n\t\tts := time.Now().Format(\"20060102-150405\")\n\t\tdumpPath := fmt.Sprintf(\"%s/req_%s_%d.json\", dumpDir, ts, reqIdx)\n\t\tos.WriteFile(dumpPath, body, 0644)\n\t\ts.logger.Printf(\"[req %d] dumped %dk to %s\", reqIdx, len(body)/1024, dumpPath)\n\t}\n\n\t// Task #7: Mark this request fingerprint for retry detection\n\tif !isRetryReq {\n\t\ts.markRequest(fp)\n\t}\n\n\t// Forward to Anthropic with SSE annotation extraction\n\t// Use original msgCount (pre-injection) so actual-based estimation stays consistent across turns\n\t// Use actual-based totalTokens as estimate when available (more accurate than full recount)\n\tfinalEstimate := totalTokens\n\tif s.sawtoothTrigger == nil || s.sawtoothTrigger.GetLastTokens(threadID) == 0 {\n\t\t// First request: no actual yet, do a full recount of post-injection messages\n\t\tfinalMsgs, _ := req[\"messages\"].([]any)\n\t\tfinalEstimate = s.measureOverhead(req) + s.countMessageTokens(finalMsgs)\n\t}\n\ts.logger.Printf(\"[req %d %s tid=%s] FINAL: ~%dk estimated (after all injections)\", reqIdx, proj, threadID, finalEstimate/1000)\n\tif raw := s.getRawEstimate(threadID); raw > 0 {\n\t\ts.cacheStatusWriter.UpdateRawForThread(threadID, raw)\n\t}\n\ts.forwardWithAnnotation(w, r, body, reqIdx, toolUseIDs, proj, threadID, msgCount, finalEstimate)\n}\n\n// getPromptFlags resolves the effective prompt flags for a profile, falling back\n// to the Server's legacy flat config if promptCfg is nil (backward compatibility).\nfunc (s *Server) getPromptFlags(profile models.PromptProfile) *config.PromptFlags {\n\tif s.promptCfg != nil {\n\t\treturn s.promptCfg.EffectivePromptFlags(profile)\n\t}\n\t// Legacy path: use flat fields as Claude defaults.\n\treturn &config.PromptFlags{\n\t\tUngate: s.cfg.PromptUngate,\n\t\tRewrite: s.cfg.PromptRewrite,\n\t\tEnhance: s.cfg.PromptEnhance,\n\t\tToolPrefs: s.cfg.PromptToolPrefs,\n\t\tOutputDiscipline: s.cfg.PromptOutputDiscipline,\n\t\tCodingDiscipline: s.cfg.PromptCodingDiscipline,\n\t\tBeweislast: s.cfg.PromptBeweislast,\n\t\tFable: s.cfg.PromptFable,\n\t\tScopeDiscipline: s.cfg.PromptScopeDiscipline,\n\t\tDelegationContract: s.cfg.PromptDelegationContract,\n\t\tClarifyFirst: s.cfg.PromptClarifyFirst,\n\t\tCodeToolsFirst: s.cfg.PromptCodeToolsFirst,\n\t\tWikiFirst: s.cfg.PromptWikiFirst,\n\t\tPatternSuggest: s.cfg.PromptPatternSuggest,\n\t}\n}\n\n// getThreadCWD returns the cached working directory for a thread.\nfunc (s *Server) getThreadCWD(threadID string) string {\n\ts.threadCWDMu.RLock()\n\tdefer s.threadCWDMu.RUnlock()\n\tif s.threadCWD == nil {\n\t\treturn \"\"\n\t}\n\treturn s.threadCWD[threadID]\n}\n\n// setThreadCWD caches the working directory for a thread.\nfunc (s *Server) setThreadCWD(threadID, cwd string) {\n\ts.threadCWDMu.Lock()\n\tdefer s.threadCWDMu.Unlock()\n\tif s.threadCWD == nil {\n\t\ts.threadCWD = make(map[string]string)\n\t}\n\ts.threadCWD[threadID] = cwd\n}\n\n// resolveOpenAITarget returns the upstream URL for an OpenAI-format request.\n// Checks ProviderTargets by prefix match (e.g. \"deepseek\" matches \"deepseek-v4-pro\"),\n// then by exact model name, then falls back to OpenAITargetURL.\nfunc (s *Server) resolveOpenAITarget(model string) string {\n\tmodelLower := strings.ToLower(model)\n\tif len(s.cfg.ProviderTargets) > 0 {\n\t\tfor key, url := range s.cfg.ProviderTargets {\n\t\t\tif url == \"\" {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tkeyLower := strings.ToLower(key)\n\t\t\tif strings.HasPrefix(modelLower, keyLower) || strings.HasPrefix(modelLower, keyLower+\"-\") {\n\t\t\t\treturn strings.TrimRight(url, \"/\")\n\t\t\t}\n\t\t}\n\t\tfor key, url := range s.cfg.ProviderTargets {\n\t\t\tif url != \"\" && strings.EqualFold(key, model) {\n\t\t\t\treturn strings.TrimRight(url, \"/\")\n\t\t\t}\n\t\t}\n\t}\n\t// Auto-discovered targets (secondary — explicit provider_targets takes priority)\n\tif len(s.autoProviderTargets) > 0 {\n\t\tif url, ok := s.autoProviderTargets[modelLower]; ok && url != \"\" {\n\t\t\treturn url\n\t\t}\n\t}\n\n\tif s.cfg.OpenAITargetURL != \"\" {\n\t\treturn s.cfg.OpenAITargetURL\n\t}\n\treturn s.cfg.TargetURL\n}\n\n// resolveAnthropicTarget returns the upstream URL for an Anthropic-format request.\n// Checks ProviderTargets by prefix match (e.g. \"deepseek\" matches \"deepseek-v4-pro\"),\n// then by exact model name, then falls back to TargetURL.\nfunc (s *Server) resolveAnthropicTarget(model string) string {\n\tmodelLower := strings.ToLower(model)\n\tif len(s.cfg.ProviderTargets) > 0 {\n\t\tfor key, url := range s.cfg.ProviderTargets {\n\t\t\tif url == \"\" {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tkeyLower := strings.ToLower(key)\n\t\t\tif strings.HasPrefix(modelLower, keyLower) || strings.HasPrefix(modelLower, keyLower+\"-\") {\n\t\t\t\treturn strings.TrimRight(url, \"/\")\n\t\t\t}\n\t\t}\n\t\tfor key, url := range s.cfg.ProviderTargets {\n\t\t\tif url != \"\" && strings.EqualFold(key, model) {\n\t\t\t\treturn strings.TrimRight(url, \"/\")\n\t\t\t}\n\t\t}\n\t}\n\treturn s.cfg.TargetURL\n}\n\n// bytesPerToken is the approximation factor for token estimation.\n// Anthropic recommends ~3.5 chars/token; we use 3.6 for slight overestimation\n// (triggers stubbing a bit earlier = safer for context budget).\nconst bytesPerToken = 3.6\n\n// TokenEstimateFunc is the central token estimation function.\n// All stubbing/budget decisions use this. Receives extracted text content\n// (not JSON wire format) so the tokenizer counts actual content tokens.\ntype TokenEstimateFunc func(text string) int\n"}, "b84dacc276d2cc93a17da6f91ff51065c9b8a15f": {"t": 1785759339.6189277, "n": 77466, "h": "5c1c97d2e943869a", "c": "package proxy\n\nimport (\n\t\"context\"\n\t\"crypto/tls\"\n\t\"encoding/json\"\n\t\"fmt\"\n\t\"io\"\n\t\"log\"\n\t\"net\"\n\t\"net/http\"\n\t\"os\"\n\t\"os/signal\"\n\t\"path/filepath\"\n\t\"strconv\"\n\t\"strings\"\n\t\"sync\"\n\t\"sync/atomic\"\n\t\"syscall\"\n\t\"time\"\n\n\t\"github.com/carsteneu/yesmem/internal/buildinfo\"\n\t\"github.com/carsteneu/yesmem/internal/config\"\n\t\"github.com/carsteneu/yesmem/internal/models\"\n\ttokenizer \"github.com/qhenkart/anthropic-tokenizer-go\"\n)\n\n// Config holds proxy configuration.\ntype Config struct {\n\tListenAddr string // e.g. \":9099\"\n\tTargetURL string // e.g. \"https://api.anthropic.com\"\n\tTokenThreshold int // trigger stubbing above this estimated token count\n\tTokenMinimumThreshold int // stub down to this floor\n\tTokenThresholds map[string]int // model-specific thresholds: {\"opus\": 180000, \"haiku\": 130000}\n\tKeepRecent int // number of messages to always keep unmodified\n\tDataDir string // yesmem data directory for DB access\n\tOpenAITargetURL string // upstream for OpenAI-format clients; if empty, uses TargetURL\n\tProviderTargets map[string]string // per-provider upstream URLs, e.g. {\"deepseek\": \"https://api.deepseek.com\"}\n\tAutoConfigureProviders bool // enable auto-discovery of provider targets from opencode config\n\n\t// Signal reflection\n\tSignalsEnabled bool // enable async signal reflection calls\n\tSignalsMode string // \"reflection\" = separate API call\n\tSignalsEveryNTurns int // every N end_turn responses\n\tSignalsModel string // full model ID, e.g. \"claude-sonnet-4-6\"\n\tAPIKey string // needed for reflection API calls\n\n\t// Sawtooth cache optimization\n\tSawtoothEnabled bool // use sawtooth instead of progressive decay (default: true)\n\tCacheTTL string // \"ephemeral\" (5m) or \"1h\" (extended)\n\n\t// Usage deflation: scale down input_tokens reported to CC to suppress \"Context low\" warning.\n\t// 0 = disabled, 0.7 = report 70% of actual tokens. Real values kept for internal tracking.\n\tUsageDeflationFactor float64\n\n\t// System prompt rewriting\n\tPromptUngate bool // strip \"may or may not be relevant\" disclaimer from CLAUDE.md injection\n\tPromptRewrite bool // strip \"Output efficiency\" + \"short and concise\", inject Ant-quality directives\n\tPromptEnhance bool // CLAUDE.md authority reinforcement, comment discipline, persona-based tone\n\tPromptToolPrefs bool // inject [yesmem-tool-prefs] Edit/Write preference + error-semantics warning\n\tPromptOutputDiscipline bool // inject [yesmem-output-discipline] no-preamble + no-skill-eval + exploratory-heuristic\n\tPromptCodingDiscipline bool // inject [yesmem-coding-discipline] read-before-propose + no-brute-force + no-half-finished\n\tPromptBeweislast bool // inject [yesmem-beweislast] fabrication-guard + claim-vs-proof + stance-under-challenge + tool-result-honesty + long-context-erosion\n\tPromptFable bool // inject [yesmem-fable] baseline-comparison + rollback-path + readback-check for high-stakes changes\n\tPromptScopeDiscipline bool // inject [yesmem-scope-discipline] deliver-A-not-A+B+C + adjacent-findings-separate + scope-bound-authorization\n\tPromptDelegationContract bool // inject [yesmem-delegation-contract] self-contained-prompts + parallel-dispatch\n\tPromptClarifyFirst bool // inject [yesmem-clarify-first] clarify only when alternative interpretations produce materially different work\n\tPromptCodeToolsFirst bool // inject [yesmem-code-tools-first] prefer MCP code-navigation tools over Agent spawns\n\tPromptWikiFirst bool // inject [yesmem-wiki-first] check per-file wiki BEFORE editing\n\tPromptPatternSuggest bool // record repeated shell-command shapes for offline cap-suggestion analysis\n\tEffortFloor string // minimum effort level: \"low\", \"medium\", \"high\", \"max\" (empty = off)\n\tSkillEvalInject string // \"true\" = verbose eval, \"silent\" = internal eval only, \"false\" = disabled\n\n\t// Cache keepalive\n\tCacheKeepaliveEnabled bool\n\tCacheKeepaliveMode string // \"auto\", \"5m\", \"1h\"\n\tCacheKeepalivePings5m int\n\tCacheKeepalivePings1h int\n\tCacheKeepaliveMinMessages int // skip keepalive when request body has fewer messages (0 = always)\n\n\t// Forked agents\n\tForkedAgentsEnabled bool\n\tForkedAgentsModel string // full model ID\n\tForkedAgentsTokenGrowthTrigger int\n\tForkedAgentsMaxFailures int\n\tForkedAgentsMaxForksPerSession int\n\tForkedAgentsDebug bool\n\n\t// Quality model fallback for forked agents\n\tQualityModelID string\n\n\t// Cache state management\n\tResetCache bool // clear persisted frozen stubs and decay state on startup\n\n\t// ModelFeatures: per-model behavioral feature gates (prefix-matched).\n\t// Falls back to FeatureDefaults for models not listed.\n\tModelFeatures map[string]*config.FeatureGates `yaml:\"model_features\"`\n\tFeatureDefaults *config.FeatureGates `yaml:\"feature_defaults\"`\n\n\t// Custom system prompt: shared SYSTEM.md template for OpenCode and Claude Code pipelines.\n\tCustomSystemPrompt CustomSystemPromptConfig\n}\n\n// CustomSystemPromptConfig controls which proxy pipelines use the SYSTEM.md template.\ntype CustomSystemPromptConfig struct {\n\tEnabledOpenCode bool\n\tEnabledClaudeCode bool\n\tEnabledCodex bool\n\tTemplatePath string\n\tModelTemplates map[string]string // per-model overrides: modelPattern → templatePath\n}\n\nconst maxAnnotations = 5000 // evict oldest when exceeded\n\n// ANSI color codes for log output\nconst (\n\tcolorReset = \"\\033[0m\"\n\tcolorDim = \"\\033[2m\" // gray — passthrough / no stubbing\n\tcolorGreen = \"\\033[32m\" // stubbed / compressed\n\tcolorLightGreen = \"\\033[92m\" // FINAL without compression\n\tcolorBlue = \"\\033[34m\" // injection\n\tcolorOrange = \"\\033[33m\" // skip / retry\n\tcolorYellow = \"\\033[33m\" // warnings\n\tcolorRed = \"\\033[31m\" // errors\n)\n\n// Server is the infinite-thread proxy.\ntype Server struct {\n\tcfg Config\n\tautoProviderTargets map[string]string // auto-discovered modelID → upstreamURL\n\thttpClient *http.Client\n\tlogger *log.Logger\n\tversion string // build version for log tracing\n\trequestIdx atomic.Int64\n\n\t// Post-hoc annotations: tool_use_id → first ~120 chars of Claude's response\n\tmu sync.RWMutex\n\tannotations map[string]string\n\n\t// Progressive decay tracker\n\tdecay *DecayTracker\n\n\t// Living narrative block\n\tnarrative *Narrative\n\n\t// Pivot moments cache (from daemon)\n\tpivotMu sync.RWMutex\n\tpivotTexts []string\n\tpivotCached time.Time\n\n\t// Aggregate stats (Task #2)\n\tstats *ProxyStats\n\n\t// Local tokenizer for accurate token counting\n\ttokenizer *tokenizer.Tokenizer\n\n\t// Cached overhead tokens (system + tools), set on first request per thread\n\toverheadMu sync.RWMutex\n\toverheadTokens int // unused, kept for interface compat\n\n\t// Hysteresis state for stubbing (Task #6)\n\tstubActive atomic.Bool\n\n\t// Daemon connection state — first successful connect flips to true\n\tdaemonReady atomic.Bool\n\n\t// Retry detection (Task #7)\n\tretryMu sync.RWMutex\n\tlastFingerprint string\n\n\t// Session start time for temporal annotation in archive blocks\n\tsessionStartMu sync.RWMutex\n\tsessionStartTime time.Time\n\n\t// Runtime config override (set via MCP set_config tool)\n\tconfigOverrideMu sync.RWMutex\n\ttokenThresholdOverrides map[string]int // model-key → threshold, \"\" = global fallback\n\n\t// Prompt rewrite miss logging: function+Claude Code version → last log time\n\trewriteMissMu sync.Mutex\n\trewriteMissLog map[string]time.Time\n\n\t// Briefing cache — keyed by threadID. Each Claude Code session thread gets\n\t// its own briefing+codemap snapshot so a sawtooth refreeze on thread A\n\t// cannot invalidate thread B's cached message-prefix hash. The cache entry\n\t// remembers which project it was loaded for; switching project for the\n\t// same thread evicts the old entry on next read.\n\tbriefingMu sync.RWMutex\n\tbriefingCache map[string]briefingEntry\n\n\t// briefingLoader is an optional test-only seam for refreshBriefing.\n\t// Nil in production → refreshBriefing falls back to s.loadBriefing.\n\tbriefingLoader func(project, projectDir string) briefingData\n\n\t// Cognitive signal bus — routes _signal_* tool calls to handlers\n\tsignalBus *SignalBus\n\n\t// Inline reflection: track which learning IDs were injected per thread\n\tlastInjectedIDsMu sync.Mutex\n\tlastInjectedIDs map[string]map[int64]string // threadID → id → source (\"briefing\"|\"associative\"|\"fresh\")\n\tsessionInjectCounts map[string]map[int64]int // threadID → learningID → injection count this session\n\tlastTurnInjected map[string]map[int64]bool // threadID → learningIDs injected in previous turn\n\n\t// Self-priming anchors: threadID → last self-prime text\n\tselfPrimeMu sync.RWMutex\n\tselfPrimes map[string]string\n\n\t// Timestamp tracking: threadID → time of last response completion\n\tresponseTsMu sync.RWMutex\n\tresponseTimes map[string]time.Time\n\n\t// Think reminder: per-thread request counter (replaces hook-think file-based counter)\n\tthinkMu sync.Mutex\n\tthinkCounters map[string]int // threadID → request count\n\n\tchannelMu sync.Mutex\n\tchannelInjectCount map[string]int // sessionID → injection turn count\n\n\t// Prompt cache gating — enables cache_control breakpoints when requests are frequent\n\tcacheGate *CacheGate\n\n\t// Sawtooth cache optimization\n\tfrozenStubs *FrozenStubs\n\teagerStubMemory *EagerStubMemory\n\tcapsCache *CapsCache\n\tsawtoothTrigger *SawtoothTrigger\n\ttimestampStore *TimestampStore\n\tcacheStatusWriter *CacheStatusWriter\n\tcacheKeepalive *CacheKeepalive\n\tcacheTTLDetector *CacheTTLDetector\n\t// promptCfg is the profile-aware prompt configuration from config.ProxyConfig.\n\t// Used to resolve EffectivePromptFlags(profile) for multi-agent prompt isolation.\n\tpromptCfg *config.ProxyConfig\n\t// threadCWD caches the working directory per thread (for opencode: only in first message)\n\tthreadCWDMu sync.RWMutex\n\tthreadCWD map[string]string // threadID → cwd\n\n\t// Cumulative token savings from collapsing (per threadID)\n\trawSavingsMu sync.Mutex\n\trawSavings map[string]int\n\n\t// Skill hint tracking: per-thread active skills\n\tskillTracker *skillHintTracker\n\n\t// Forked agent state: per-thread token growth + failure tracking\n\tforkState *ForkState\n\tforkConfigs []ForkConfig\n\n\t// Custom system prompt template for OpenCode (loaded from DataDir/SYSTEM.md)\n\tcustomSystemPrompt []byte\n\n\t// Per-model template overrides (loaded from config CustomSystemPrompt.ModelTemplates)\n\tmodelTemplates map[string][]byte\n\n\t// Rules re-injection: condensed CLAUDE.md rules injected every ~40k tokens\n\trulesMu sync.RWMutex\n\trulesBlock string // cached condensed rules (fetched once from daemon)\n\trulesTokenCount map[string]int // threadID → tokens since last rules injection\n\trulesCollapseInjected map[string]bool // threadID → true if collapse already injected rules (reset by normal inject)\n\tmsgCounters *msgCounters // global per-thread msg counter, persists across collapses\n\n\t// Loop detection: per-thread warning state\n\tloopMu sync.Mutex\n\tloopStates map[string]*LoopState // threadID → state\n\n\t// Injection overhead: per-thread delta between API-actual and local BPE estimate\n\tinjectionOverheadMu sync.RWMutex\n\tinjectionOverhead map[string]int // threadID → overhead tokens\n}\n\n// Run starts the proxy server and blocks until interrupted.\nfunc Run(cfg Config) error {\n\ts := &Server{\n\t\tcfg: cfg,\n\t\thttpClient: &http.Client{\n\t\t\tTimeout: 10 * time.Minute,\n\t\t\tTransport: &http.Transport{\n\t\t\t\tTLSClientConfig: &tls.Config{},\n\t\t\t\tMaxIdleConns: 10,\n\t\t\t\tIdleConnTimeout: 90 * time.Second,\n\t\t\t\tDisableCompression: true,\n\t\t\t\tResponseHeaderTimeout: 60 * time.Second,\n\t\t\t},\n\t\t},\n\t\tlogger: createLogger(cfg.DataDir),\n\t\tannotations: make(map[string]string),\n\t\tdecay: NewDecayTracker(),\n\t\tnarrative: NewNarrative(),\n\t\tstats: &ProxyStats{startTime: time.Now()},\n\t\tselfPrimes: make(map[string]string),\n\t\tlastInjectedIDs: make(map[string]map[int64]string),\n\t\tversion: buildinfo.Version,\n\t\tsessionInjectCounts: make(map[string]map[int64]int),\n\t\tlastTurnInjected: make(map[string]map[int64]bool),\n\t\tresponseTimes: make(map[string]time.Time),\n\t\tthinkCounters: make(map[string]int),\n\t\tchannelInjectCount: make(map[string]int),\n\t\trewriteMissLog: make(map[string]time.Time),\n\t\tcacheGate: NewCacheGate(cacheGapForTTL(cfg.CacheTTL)),\n\t\tfrozenStubs: NewFrozenStubsWithTTL(sawtoothTTLForCacheTTL(cfg.CacheTTL)),\n\t\teagerStubMemory: NewEagerStubMemory(),\n\t\tcapsCache: NewCapsCache(),\n\t\ttimestampStore: NewTimestampStore(),\n\t\tsawtoothTrigger: NewSawtoothTrigger(cacheGapForTTL(cfg.CacheTTL), cfg.TokenThreshold, cfg.TokenMinimumThreshold),\n\t\tcacheStatusWriter: NewCacheStatusWriter(cfg.DataDir, cfg.CacheTTL, cfg.TokenThreshold, cfg.TokenMinimumThreshold),\n\t\tcacheTTLDetector: NewCacheTTLDetectorWithPersist(cfg.DataDir),\n\t\tskillTracker: newSkillHintTracker(),\n\t\trulesTokenCount: make(map[string]int),\n\t\trulesCollapseInjected: make(map[string]bool),\n\t\tmsgCounters: newMsgCounters(),\n\t\tloopStates: make(map[string]*LoopState),\n\t\tinjectionOverhead: make(map[string]int),\n\t\tforkState: NewForkState(cfg.ForkedAgentsTokenGrowthTrigger, 60000, cfg.ForkedAgentsMaxFailures, cfg.ForkedAgentsMaxForksPerSession),\n\t\tforkConfigs: []ForkConfig{},\n\t}\n\n\t// Load custom system prompt from configured path\n\tif cfg.CustomSystemPrompt.TemplatePath != \"\" &&\n\t\t(cfg.CustomSystemPrompt.EnabledOpenCode || cfg.CustomSystemPrompt.EnabledClaudeCode || cfg.CustomSystemPrompt.EnabledCodex) {\n\t\ts.customSystemPrompt = loadSystemPromptFromPath(cfg.CustomSystemPrompt.TemplatePath)\n\t\tif s.customSystemPrompt != nil {\n\t\t\ts.logger.Printf(\"[proxy] loaded custom system prompt from %s (%d bytes, opencode=%v cc=%v codex=%v)\",\n\t\t\t\tcfg.CustomSystemPrompt.TemplatePath, len(s.customSystemPrompt),\n\t\t\t\tcfg.CustomSystemPrompt.EnabledOpenCode, cfg.CustomSystemPrompt.EnabledClaudeCode, cfg.CustomSystemPrompt.EnabledCodex)\n\t\t} else {\n\t\t\ts.logger.Printf(\"[proxy] WARNING: custom_system_prompt enabled but template not found at %q — feature disabled\",\n\t\t\t\tcfg.CustomSystemPrompt.TemplatePath)\n\t\t}\n\t}\n\n\t// Load per-model template overrides\n\tif len(cfg.CustomSystemPrompt.ModelTemplates) > 0 {\n\t\ts.modelTemplates = make(map[string][]byte, len(cfg.CustomSystemPrompt.ModelTemplates))\n\t\tfor pattern, path := range cfg.CustomSystemPrompt.ModelTemplates {\n\t\t\ttpl := loadSystemPromptFromPath(path)\n\t\t\tif tpl != nil {\n\t\t\t\ts.modelTemplates[pattern] = tpl\n\t\t\t\ts.logger.Printf(\"[proxy] loaded model template %q from %s (%d bytes)\", pattern, path, len(tpl))\n\t\t\t} else {\n\t\t\t\ts.logger.Printf(\"[proxy] WARNING: model template %q not found at %q — skipping\", pattern, path)\n\t\t\t}\n\t\t}\n\t}\n\n\t// Log persisted detection state\n\tif sup := s.cacheTTLDetector.Is1hSupported(); sup != nil {\n\t\ts.logger.Printf(\"[proxy] loaded persisted TTL detection: 1h_supported=%v\", *sup)\n\t\tif *sup && s.frozenStubs != nil {\n\t\t\ts.frozenStubs.UpdateTTL(sawtoothTTLForCacheTTL(\"1h\"))\n\t\t}\n\t}\n\n\tif cfg.CacheKeepaliveEnabled {\n\t\ts.cacheKeepalive = NewCacheKeepalive(CacheKeepaliveConfig{\n\t\t\tTarget: cfg.TargetURL,\n\t\t\tProviderTargets: cfg.ProviderTargets,\n\t\t\tMode: cfg.CacheKeepaliveMode,\n\t\t\tPings5m: cfg.CacheKeepalivePings5m,\n\t\t\tPings1h: cfg.CacheKeepalivePings1h,\n\t\t\tMinMessages: cfg.CacheKeepaliveMinMessages,\n\t\t\tDetector: s.cacheTTLDetector,\n\t\t\tLogger: s.logger,\n\t\t\tOnPing: func(threadID string, cacheRead, cacheWrite int) {\n\t\t\t\ts.cacheTTLDetector.RecordResponse(cacheRead, cacheWrite, 0)\n\t\t\t\tif s.frozenStubs != nil {\n\t\t\t\t\ts.frozenStubs.Touch(threadID)\n\t\t\t\t}\n\t\t\t\tif s.sawtoothTrigger != nil {\n\t\t\t\t\ts.sawtoothTrigger.TouchRequestTime(threadID)\n\t\t\t\t}\n\t\t\t},\n\t\t})\n\t}\n\n\t// Wire detector + keepalive to status writer for terminal display\n\ts.cacheStatusWriter.SetDetector(s.cacheTTLDetector)\n\tif s.cacheKeepalive != nil {\n\t\ts.cacheStatusWriter.SetKeepalive(s.cacheKeepalive)\n\t}\n\n\t// Register forked agent configs\n\tif cfg.ForkedAgentsEnabled {\n\t\tmodel := cfg.ForkedAgentsModel\n\t\ts.forkConfigs = append(s.forkConfigs, NewExtractAndEvaluateConfig(model))\n\t\tmodelDesc := model\n\t\tif modelDesc == \"\" {\n\t\t\tmodelDesc = \"(same as main thread)\"\n\t\t}\n\t\ts.logger.Printf(\"[proxy] forked agents enabled: %d configs, model=%s, trigger=%dk tokens\",\n\t\t\tlen(s.forkConfigs), modelDesc, cfg.ForkedAgentsTokenGrowthTrigger/1000)\n\t}\n\n\t// Wire sawtooth token persistence via daemon RPC\n\ts.sawtoothTrigger.SetPersistFunc(func(key, value string) {\n\t\tif _, err := s.queryDaemon(\"set_proxy_state\", map[string]any{\"key\": key, \"value\": value}); err != nil {\n\t\t\ts.logger.Printf(\"[sawtooth] persist failed for %s: %v\", key, err)\n\t\t}\n\t})\n\ts.sawtoothTrigger.SetLoadFunc(func(key string) (string, bool) {\n\t\tresult, err := s.queryDaemon(\"get_proxy_state\", map[string]any{\"key\": key})\n\t\tif err != nil || result == nil {\n\t\t\treturn \"\", false\n\t\t}\n\t\tvar resp struct {\n\t\t\tValue string `json:\"value\"`\n\t\t}\n\t\tif json.Unmarshal(result, &resp) != nil || resp.Value == \"\" {\n\t\t\treturn \"\", false\n\t\t}\n\t\treturn resp.Value, true\n\t})\n\n\t// Wire frozen stubs persistence via same daemon RPC\n\ts.frozenStubs.SetPersistFunc(func(key, value string) {\n\t\tif _, err := s.queryDaemon(\"set_proxy_state\", map[string]any{\"key\": key, \"value\": value}); err != nil {\n\t\t\ts.logger.Printf(\"[frozen] persist failed for %s: %v\", key, err)\n\t\t}\n\t})\n\ts.frozenStubs.SetLoadFunc(func(key string) (string, bool) {\n\t\tresult, err := s.queryDaemon(\"get_proxy_state\", map[string]any{\"key\": key})\n\t\tif err != nil || result == nil {\n\t\t\treturn \"\", false\n\t\t}\n\t\tvar resp struct {\n\t\t\tValue string `json:\"value\"`\n\t\t}\n\t\tif json.Unmarshal(result, &resp) != nil || resp.Value == \"\" {\n\t\t\treturn \"\", false\n\t\t}\n\t\treturn resp.Value, true\n\t})\n\n\t// Wire eager-stub memory persistence via same daemon RPC.\n\t// Persists per-thread tool_use_id sets so stub decisions survive deploys.\n\ts.eagerStubMemory.SetPersistFunc(func(key, value string) {\n\t\tif _, err := s.queryDaemon(\"set_proxy_state\", map[string]any{\"key\": key, \"value\": value}); err != nil {\n\t\t\ts.logger.Printf(\"[eagerstub] persist failed for %s: %v\", key, err)\n\t\t}\n\t})\n\ts.eagerStubMemory.SetLoadFunc(func(key string) (string, bool) {\n\t\tresult, err := s.queryDaemon(\"get_proxy_state\", map[string]any{\"key\": key})\n\t\tif err != nil || result == nil {\n\t\t\treturn \"\", false\n\t\t}\n\t\tvar resp struct {\n\t\t\tValue string `json:\"value\"`\n\t\t}\n\t\tif json.Unmarshal(result, &resp) != nil || resp.Value == \"\" {\n\t\t\treturn \"\", false\n\t\t}\n\t\treturn resp.Value, true\n\t})\n\n\t// Wire decay tracker persistence via same daemon RPC\n\ts.decay.SetPersistFunc(func(key, value string) {\n\t\tif _, err := s.queryDaemon(\"set_proxy_state\", map[string]any{\"key\": key, \"value\": value}); err != nil {\n\t\t\ts.logger.Printf(\"[decay] persist failed for %s: %v\", key, err)\n\t\t}\n\t})\n\ts.decay.SetLoadFunc(func(key string) (string, bool) {\n\t\tresult, err := s.queryDaemon(\"get_proxy_state\", map[string]any{\"key\": key})\n\t\tif err != nil || result == nil {\n\t\t\treturn \"\", false\n\t\t}\n\t\tvar resp struct {\n\t\t\tValue string `json:\"value\"`\n\t\t}\n\t\tif json.Unmarshal(result, &resp) != nil || resp.Value == \"\" {\n\t\t\treturn \"\", false\n\t\t}\n\t\treturn resp.Value, true\n\t})\n\n\t// Wire timestamp store persistence via same daemon RPC\n\ts.timestampStore.SetPersistFunc(func(key, value string) {\n\t\tif _, err := s.queryDaemon(\"set_proxy_state\", map[string]any{\"key\": key, \"value\": value}); err != nil {\n\t\t\ts.logger.Printf(\"[timestamps] persist failed for %s: %v\", key, err)\n\t\t}\n\t})\n\ts.timestampStore.SetLoadFunc(func(key string) (string, bool) {\n\t\tresult, err := s.queryDaemon(\"get_proxy_state\", map[string]any{\"key\": key})\n\t\tif err != nil || result == nil {\n\t\t\treturn \"\", false\n\t\t}\n\t\tvar resp struct {\n\t\t\tValue string `json:\"value\"`\n\t\t}\n\t\tif json.Unmarshal(result, &resp) != nil || resp.Value == \"\" {\n\t\t\treturn \"\", false\n\t\t}\n\t\treturn resp.Value, true\n\t})\n\n\t// Clear persisted cache state if --reset-cache was passed\n\tif cfg.ResetCache {\n\t\tfor _, prefix := range []string{\"frozen:\", \"decay:\", \"timestamps:\"} {\n\t\t\tresult, err := s.queryDaemon(\"delete_proxy_state_prefix\", map[string]any{\"prefix\": prefix})\n\t\t\tif err != nil {\n\t\t\t\ts.logger.Printf(\"[reset-cache] failed to delete %s*: %v\", prefix, err)\n\t\t\t} else {\n\t\t\t\tvar resp struct{ Deleted int }\n\t\t\t\tjson.Unmarshal(result, &resp)\n\t\t\t\ts.logger.Printf(\"[reset-cache] deleted %d %s* entries\", resp.Deleted, prefix)\n\t\t\t}\n\t\t}\n\t}\n\n\t// Init signal bus with daemon RPC as callback\n\ts.signalBus = NewSignalBus(s.logger)\n\tregisterSignalHandlers(s.signalBus, s.logger, s.queryDaemon, func(threadID, anchor string) {\n\t\ts.selfPrimeMu.Lock()\n\t\ts.selfPrimes[threadID] = anchor\n\t\ts.selfPrimeMu.Unlock()\n\t})\n\n\t// Init local tokenizer (BPE-based, ~60ms)\n\ttok, err := tokenizer.New()\n\tif err != nil {\n\t\ts.logger.Printf(\"%s[proxy] tokenizer init failed, falling back to heuristic: %v%s\", colorOrange, err, colorReset)\n\t} else {\n\t\ts.tokenizer = tok\n\t\ts.logger.Printf(\"[proxy] tokenizer initialized\")\n\t}\n\n\t// Auto-discover provider targets from opencode config\n\tif cfg.AutoConfigureProviders {\n\t\ts.autoProviderTargets = runAutoDiscovery(s.logger, cfg.ProviderTargets)\n\t} else {\n\t\ts.logger.Printf(\"[proxy] auto_configure_providers is disabled — skipping auto-discovery\")\n\t}\n\n\tmux := http.NewServeMux()\n\tmux.HandleFunc(\"/health\", s.handleHealth)\n\tmux.HandleFunc(\"/\", s.handleRequest)\n\n\tsrv := &http.Server{\n\t\tAddr: sanitizeListenAddr(cfg.ListenAddr),\n\t\tHandler: mux,\n\t}\n\n\tdone := make(chan os.Signal, 1)\n\tsignal.Notify(done, syscall.SIGINT, syscall.SIGTERM)\n\n\tgo func() {\n\t\t<-done\n\t\ts.logger.Println(\"shutting down (draining in-flight requests)...\")\n\t\tif s.cacheKeepalive != nil {\n\t\t\ts.cacheKeepalive.Stop()\n\t\t}\n\t\tctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)\n\t\tdefer cancel()\n\t\tif err := srv.Shutdown(ctx); err != nil {\n\t\t\ts.logger.Printf(\"graceful shutdown failed: %v, forcing close\", err)\n\t\t\tsrv.Close()\n\t\t}\n\t}()\n\n\ts.logger.Printf(\"listening on %s → %s (threshold=%d, keepRecent=%d, model_thresholds=%v)\",\n\t\tsanitizeListenAddr(cfg.ListenAddr), cfg.TargetURL, cfg.TokenThreshold, cfg.KeepRecent, cfg.TokenThresholds)\n\n\tif err := srv.ListenAndServe(); err != http.ErrServerClosed {\n\t\treturn fmt.Errorf(\"listen: %w\", err)\n\t}\n\treturn nil\n}\n\n// effectiveTokenThreshold returns the runtime token threshold for a given model.\n// Priority: session override (via set_config with session_id) > model-specific override (MCP set_config token_threshold:)\n// > model-specific override (config TokenThresholds) > global override (set_config token_threshold) > config global default.\nfunc (s *Server) effectiveTokenThreshold(model string) int {\n\ts.configOverrideMu.RLock()\n\toverrides := s.tokenThresholdOverrides\n\ts.configOverrideMu.RUnlock()\n\t// 1. MCP override (model-specific)\n\tif len(overrides) > 0 && model != \"\" {\n\t\tlower := strings.ToLower(model)\n\t\tfor key, threshold := range overrides {\n\t\t\tif key != \"\" && strings.Contains(lower, strings.ToLower(key)) && threshold > 0 {\n\t\t\t\treturn threshold\n\t\t\t}\n\t\t}\n\t}\n\t// 2. Config model-specific\n\tif model != \"\" && len(s.cfg.TokenThresholds) > 0 {\n\t\tlower := strings.ToLower(model)\n\t\tfor key, threshold := range s.cfg.TokenThresholds {\n\t\t\tif strings.Contains(lower, strings.ToLower(key)) {\n\t\t\t\treturn threshold\n\t\t\t}\n\t\t}\n\t}\n\t// 3. Global override fallback (key \"\")\n\tif len(overrides) > 0 {\n\t\tif globalOverride, ok := overrides[\"\"]; ok && globalOverride > 0 {\n\t\t\treturn globalOverride\n\t\t}\n\t}\n\t// 4. Config global default\n\treturn s.cfg.TokenThreshold\n}\n\n// SetTokenThresholdOverride sets a runtime override for the token threshold per model key.\n// Pass threshold=0 to reset the override for that key. Key \"\" is the global fallback.\nfunc (s *Server) SetTokenThresholdOverride(modelKey string, threshold int) {\n\ts.configOverrideMu.Lock()\n\tif s.tokenThresholdOverrides == nil {\n\t\ts.tokenThresholdOverrides = make(map[string]int)\n\t}\n\tif threshold <= 0 {\n\t\tdelete(s.tokenThresholdOverrides, modelKey)\n\t} else {\n\t\ts.tokenThresholdOverrides[modelKey] = threshold\n\t}\n\ts.configOverrideMu.Unlock()\n\n\t// Propagate global threshold to sawtooth trigger so runtime overrides take effect\n\tif modelKey == \"\" && s.sawtoothTrigger != nil {\n\t\tif threshold > 0 {\n\t\t\ts.sawtoothTrigger.SetTokenThreshold(threshold)\n\t\t} else {\n\t\t\ts.sawtoothTrigger.SetTokenThreshold(s.cfg.TokenThreshold)\n\t\t}\n\t}\n}\n\n// refreshConfigOverrides loads runtime config overrides from daemon proxy_state.\n// Checks in priority order: session-specific, model-specific, then global.\nfunc (s *Server) refreshConfigOverrides(threadID string) {\n\t// 1. Session-specific override (highest priority)\n\tif threadID != \"\" {\n\t\tresult, err := s.queryDaemon(\"get_proxy_state\", map[string]any{\"key\": \"config_override:token_threshold:\" + threadID})\n\t\tif err == nil && result != nil {\n\t\t\tvar resp struct {\n\t\t\t\tValue string `json:\"value\"`\n\t\t\t}\n\t\t\tif json.Unmarshal(result, &resp) == nil && resp.Value != \"\" {\n\t\t\t\tif v, err := strconv.Atoi(resp.Value); err == nil && v > 0 {\n\t\t\t\t\ts.SetTokenThresholdOverride(\"\", v)\n\t\t\t\t\treturn\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\t// 2. Model-specific overrides (e.g. config_override:token_threshold:deepseek)\n\tfor modelKey := range s.cfg.TokenThresholds {\n\t\tresult, err := s.queryDaemon(\"get_proxy_state\", map[string]any{\"key\": \"config_override:token_threshold:\" + modelKey})\n\t\tif err == nil && result != nil {\n\t\t\tvar resp struct {\n\t\t\t\tValue string `json:\"value\"`\n\t\t\t}\n\t\t\tif json.Unmarshal(result, &resp) == nil && resp.Value != \"\" {\n\t\t\t\tif v, err := strconv.Atoi(resp.Value); err == nil && v > 0 {\n\t\t\t\t\ts.SetTokenThresholdOverride(modelKey, v)\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\t// 3. Global override (fallback)\n\tresult, err := s.queryDaemon(\"get_proxy_state\", map[string]any{\"key\": \"config_override:token_threshold\"})\n\tif err != nil || result == nil {\n\t\treturn\n\t}\n\tvar resp struct {\n\t\tValue string `json:\"value\"`\n\t}\n\tif json.Unmarshal(result, &resp) == nil && resp.Value != \"\" {\n\t\tif v, err := strconv.Atoi(resp.Value); err == nil && v > 0 {\n\t\t\ts.SetTokenThresholdOverride(\"\", v)\n\t\t}\n\t}\n}\n\n// queryDaemon opens a fresh unix socket connection per call (thread-safe, no import cycle).\n// ensureFrozenPersisted checks if frozen stubs for a thread are in the DB.\n// If missing (e.g. daemon was unreachable after deploy), re-persists them.\n// Runs asynchronously to avoid blocking the request path.\nfunc (s *Server) ensureFrozenPersisted(threadID string) {\n\tgo func() {\n\t\tkey := \"frozen:\" + threadID\n\t\tresult, err := s.queryDaemon(\"get_proxy_state\", map[string]any{\"key\": key})\n\t\tif err != nil {\n\t\t\treturn // daemon still unreachable, will retry next request\n\t\t}\n\t\tvar resp struct{ Value string }\n\t\tif json.Unmarshal(result, &resp) == nil && resp.Value != \"\" {\n\t\t\treturn // already persisted\n\t\t}\n\t\t// Not in DB — trigger re-persist from in-memory state\n\t\ts.frozenStubs.Persist(threadID)\n\t\ts.decay.Persist(threadID)\n\t\ts.logger.Printf(\"[frozen] re-persisted stubs for %s (was missing from DB)\", threadID)\n\t}()\n}\n\nfunc (s *Server) queryDaemon(method string, params map[string]any) (json.RawMessage, error) {\n\tif s.cfg.DataDir == \"\" {\n\t\treturn nil, fmt.Errorf(\"no data dir configured\")\n\t}\n\tsockPath := filepath.Join(s.cfg.DataDir, \"daemon.sock\")\n\n\t// On cold start (daemon not yet confirmed ready), retry longer to survive daemon restart\n\tmaxAttempts := 3\n\tif !s.daemonReady.Load() {\n\t\tmaxAttempts = 30 // 30 x 500ms = 15s max wait for daemon startup\n\t}\n\n\tvar conn net.Conn\n\tvar err error\n\tfor attempt := 0; attempt < maxAttempts; attempt++ {\n\t\tconn, err = net.DialTimeout(\"unix\", sockPath, 3*time.Second)\n\t\tif err == nil {\n\t\t\ts.daemonReady.Store(true)\n\t\t\tbreak\n\t\t}\n\t\tif attempt < maxAttempts-1 {\n\t\t\ttime.Sleep(500 * time.Millisecond)\n\t\t}\n\t}\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"dial daemon: %w\", err)\n\t}\n\tdefer conn.Close()\n\t// Briefing generation can take 10-20s (LLM refinement + scanner);\n\t// other RPCs (search, learnings) are fast and fine with 5s.\n\tdeadline := 5 * time.Second\n\tif method == \"generate_briefing\" {\n\t\tdeadline = 30 * time.Second\n\t}\n\tconn.SetDeadline(time.Now().Add(deadline))\n\n\t// JSON-RPC style request/response (matches daemon.SocketServer protocol)\n\ttype request struct {\n\t\tMethod string `json:\"method\"`\n\t\tParams map[string]any `json:\"params,omitempty\"`\n\t}\n\ttype response struct {\n\t\tResult json.RawMessage `json:\"result,omitempty\"`\n\t\tError string `json:\"error,omitempty\"`\n\t}\n\n\tif err := json.NewEncoder(conn).Encode(request{Method: method, Params: params}); err != nil {\n\t\treturn nil, fmt.Errorf(\"send: %w\", err)\n\t}\n\n\tvar resp response\n\tif err := json.NewDecoder(conn).Decode(&resp); err != nil {\n\t\treturn nil, fmt.Errorf(\"recv: %w\", err)\n\t}\n\tif resp.Error != \"\" {\n\t\treturn nil, fmt.Errorf(\"daemon: %s\", resp.Error)\n\t}\n\treturn resp.Result, nil\n}\n\nfunc (s *Server) handleRequest(w http.ResponseWriter, r *http.Request) {\n\tif isResponsesPath(r) {\n\t\ts.handleResponses(w, r)\n\t\treturn\n\t}\n\tif isOpenAIPath(r) {\n\t\ts.handleOpenAICompletions(w, r)\n\t\treturn\n\t}\n\tif r.Method == \"POST\" && strings.HasPrefix(r.URL.Path, \"/v1/messages\") &&\n\t\t!strings.Contains(r.URL.Path, \"/batches\") &&\n\t\t!strings.Contains(r.URL.Path, \"/count_tokens\") {\n\t\ts.handleMessages(w, r)\n\t\treturn\n\t}\n\ts.passthrough(w, r)\n}\n\nfunc (s *Server) handleMessages(w http.ResponseWriter, r *http.Request) {\n\treqIdx := int(s.requestIdx.Add(1))\n\n\t// Track session start time (first request sets it)\n\ts.sessionStartMu.Lock()\n\tif s.sessionStartTime.IsZero() {\n\t\ts.sessionStartTime = time.Now()\n\t}\n\ts.sessionStartMu.Unlock()\n\n\t// Sawtooth: periodic TTL eviction of frozen stubs\n\tif s.cfg.SawtoothEnabled && reqIdx%50 == 0 {\n\t\tif n := s.frozenStubs.Evict(); n > 0 {\n\t\t\ts.logger.Printf(\"[req %d] SAWTOOTH: evicted %d stale frozen stubs\", reqIdx, n)\n\t\t}\n\t}\n\n\t// Task #9: Bypass — skip all proxy logic\n\tif isBypassed(r.Header) {\n\t\ts.logger.Printf(\"%s[req %d] bypass active, passthrough%s\", colorDim, reqIdx, colorReset)\n\t\tbody, err := io.ReadAll(r.Body)\n\t\tr.Body.Close()\n\t\tif err != nil {\n\t\t\thttp.Error(w, \"failed to read request body\", http.StatusBadGateway)\n\t\t\treturn\n\t\t}\n\t\ts.forwardRaw(w, r, body)\n\t\treturn\n\t}\n\n\tbody, err := io.ReadAll(r.Body)\n\tr.Body.Close()\n\tif err != nil {\n\t\ts.logger.Printf(\"[req %d] read body error: %v\", reqIdx, err)\n\t\thttp.Error(w, \"failed to read request body\", http.StatusBadGateway)\n\t\treturn\n\t}\n\n\t// Parse JSON\n\tvar req map[string]any\n\tif err := json.Unmarshal(body, &req); err != nil {\n\t\ts.logger.Printf(\"%s[req %d] JSON parse error, passing through: %v%s\", colorDim, reqIdx, err, colorReset)\n\t\ts.forwardRaw(w, r, body)\n\t\treturn\n\t}\n\n\tmessages, ok := req[\"messages\"].([]any)\n\tif !ok {\n\t\ts.forwardRaw(w, r, body)\n\t\treturn\n\t}\n\n\treq[\"messages\"] = messages\n\n\t// Count ALL raw messages BEFORE sawtooth — this gives the correct total msg:N\n\t// for the entire session (CC sends all messages, proxy compacts later).\n\trawMsgCount := len(messages)\n\n\t// Use session_id directly as thread ID (unique per CC session).\n\t// Prefer X-Claude-Code-Session-Id header (CC v2.1.86+), fallback to body metadata.\n\tthreadID := extractSessionID(req, r.Header.Get(\"X-Claude-Code-Session-Id\"), \"\")\n\tif threadID == \"\" {\n\t\tthreadID = DeriveThreadID(req)\n\t}\n\tproj := extractProjectName(req)\n\tmodel, _ := req[\"model\"].(string)\n\n\t// Persist current model under session-keyed proxy_state so handleWhoami\n\t// can return it. Best-effort: failure is non-fatal (log only).\n\tif threadID != \"\" && model != \"\" {\n\t\tif _, err := s.queryDaemon(\"set_proxy_state\", map[string]any{\n\t\t\t\"key\": \"session_model:\" + threadID,\n\t\t\t\"value\": model,\n\t\t}); err != nil {\n\t\t\ts.logger.Printf(\"[whoami-model] persist failed for %s: %v\", threadID, err)\n\t\t}\n\t}\n\n\t// Refresh runtime config overrides from daemon (per-session or global)\n\ts.refreshConfigOverrides(threadID)\n\n\t// Track whether req has been modified and needs re-serialization\n\tneedsReserialization := false\n\n\t// Message count at request entry (used for actual-based token estimation)\n\tmsgCount := len(messages)\n\n\t// Log message count + cache breakpoint positions for diagnostics\n\ts.logger.Printf(\"req %d: %d messages total\", reqIdx, msgCount)\n\tlogCacheBreakpointLocations(req, s.logger)\n\n\t// Pre-modification dump: capture the raw request before any proxy changes.\n\tif os.Getenv(\"YESMEM_PROXY_DEBUG\") == \"1\" {\n\t\tif preBody, err := json.Marshal(req); err == nil {\n\t\t\tdumpDir := filepath.Join(s.cfg.DataDir, \"debug\")\n\t\t\tos.MkdirAll(dumpDir, 0755)\n\t\t\tts := time.Now().Format(\"20060102-150405\")\n\t\t\tdumpPath := fmt.Sprintf(\"%s/req_%s_%d_pre.json\", dumpDir, ts, reqIdx)\n\t\t\tos.WriteFile(dumpPath, preBody, 0644)\n\t\t\ts.logger.Printf(\"[req %d] pre-dump %dk to %s\", reqIdx, len(preBody)/1024, dumpPath)\n\t\t}\n\t}\n\n\t// Cache bug mitigations (CC Bun fork issues):\n\t// Bug 1: Normalize cch= sentinel patterns in message content to prevent\n\t// Bun's string replacement from corrupting the wrong occurrence.\n\tif SanitizeBillingSentinel(messages) {\n\t\ts.logger.Printf(\"[req %d] CACHE: sanitized billing sentinel in message content\", reqIdx)\n\t\tneedsReserialization = true\n\t}\n\t// Bug 2: Normalize system[0] billing hash to a fixed value so the cache\n\t// prefix stays stable across fresh/resume session boundaries.\n\tif NormalizeBillingHeader(req) {\n\t\ts.logger.Printf(\"[req %d] CACHE: normalized billing header cch= hash\", reqIdx)\n\t\tneedsReserialization = true\n\t}\n\n\t// Profile-aware prompt flags: Claude profile merges shared_prompt + claude_prompt + legacy flat fields.\n\tpfInject := s.getPromptFlags(models.ProfileClaude)\n\n\t// CC custom system prompt replacement: must run before strip/rewrite so replaced blocks\n\t// don't trigger false-positive strip/rewrite calls.\n\ts.applyCCSystemPrompt(req)\n\n\t// Strip role:system messages unconditionally — must run on every request.\n\t// StripReminders (inside runStubCycle) only runs on long conversations.\n\tif filtered := FilterSystemRoleMessages(messages); len(filtered) != len(messages) {\n\t\tmessages = filtered\n\t\treq[\"messages\"] = messages\n\t\tneedsReserialization = true\n\t}\n\n\t// prompt_ungate: strip the CLAUDE.md subordination disclaimer so user instructions carry full authority.\n\tif pfInject.Ungate {\n\t\tif StripCLAUDEMDDisclaimer(req) {\n\t\t\ts.logger.Printf(\"[req %d] SYSTEM: stripped CLAUDE.md disclaimer\", reqIdx)\n\t\t\tneedsReserialization = true\n\t\t}\n\t}\n\n\t// prompt_rewrite: strip output-throttling directives, rewrite quality caps, inject Ant-quality directives\n\tif pfInject.Rewrite {\n\t\t// When CC custom system prompt is active, the system prompt has been replaced with SYSTEM.md.\n\t\t// Strip/rewrite calls would not find their target strings — skip to avoid log noise.\n\t\tif !s.cfg.CustomSystemPrompt.EnabledClaudeCode {\n\t\t\tuserAgent := r.Header.Get(\"User-Agent\")\n\t\t\tif StripOutputEfficiency(req) {\n\t\t\t\ts.logger.Printf(\"[req %d] REWRITE: stripped Output efficiency section\", reqIdx)\n\t\t\t\tneedsReserialization = true\n\t\t\t} else {\n\t\t\t\ts.logRewriteMiss(\"StripOutputEfficiency\", userAgent)\n\t\t\t}\n\t\t\tif StripToneBrevity(req) {\n\t\t\t\ts.logger.Printf(\"[req %d] REWRITE: stripped 'short and concise' from Tone\", reqIdx)\n\t\t\t\tneedsReserialization = true\n\t\t\t} else {\n\t\t\t\ts.logRewriteMiss(\"StripToneBrevity\", userAgent)\n\t\t\t}\n\t\t\tif RewriteGoldPlating(req) {\n\t\t\t\ts.logger.Printf(\"[req %d] REWRITE: rewritten gold-plating directive\", reqIdx)\n\t\t\t\tneedsReserialization = true\n\t\t\t}\n\t\t\tif RewriteErrorHandling(req) {\n\t\t\t\ts.logger.Printf(\"[req %d] REWRITE: rewritten error handling directive\", reqIdx)\n\t\t\t\tneedsReserialization = true\n\t\t\t}\n\t\t\tif RewriteThreeLinesRule(req) {\n\t\t\t\ts.logger.Printf(\"[req %d] REWRITE: rewritten three-lines rule\", reqIdx)\n\t\t\t\tneedsReserialization = true\n\t\t\t}\n\t\t\tif RewriteSubagentCompleteness(req) {\n\t\t\t\ts.logger.Printf(\"[req %d] REWRITE: rewritten subagent completeness\", reqIdx)\n\t\t\t\tneedsReserialization = true\n\t\t\t}\n\t\t\tif RewriteExploreAgentSpeed(req) {\n\t\t\t\ts.logger.Printf(\"[req %d] REWRITE: rewritten explore agent speed bias\", reqIdx)\n\t\t\t\tneedsReserialization = true\n\t\t\t}\n\t\t\tif RewriteSubagentCodeSuppression(req) {\n\t\t\t\ts.logger.Printf(\"[req %d] REWRITE: rewritten subagent code suppression\", reqIdx)\n\t\t\t\tneedsReserialization = true\n\t\t\t}\n\t\t\tif RewriteScopeMatching(req) {\n\t\t\t\ts.logger.Printf(\"[req %d] REWRITE: rewritten scope matching\", reqIdx)\n\t\t\t\tneedsReserialization = true\n\t\t\t}\n\t\t} // end !EnabledClaudeCode guard\n\t\tInjectAntDirectives(req)\n\t\ts.logger.Printf(\"[req %d] REWRITE: injected Ant-quality directives\", reqIdx)\n\t\tneedsReserialization = true\n\t}\n\n\t// prompt_enhance: CLAUDE.md authority + comment discipline + persona tone\n\tif pfInject.Enhance {\n\t\tInjectCLAUDEMDAuthority(req)\n\t\tif raw, err := s.queryDaemon(\"get_persona\", nil); err == nil {\n\t\t\tvar persona map[string]any\n\t\t\tif json.Unmarshal(raw, &persona) == nil {\n\t\t\t\tif traits, ok := persona[\"traits\"].(map[string]any); ok {\n\t\t\t\t\tif verbosity, ok := traits[\"verbosity\"].(string); ok {\n\t\t\t\t\t\tInjectPersonaTone(req, verbosity)\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\ts.logger.Printf(\"[req %d] ENHANCE: injected authority + persona tone\", reqIdx)\n\t\tneedsReserialization = true\n\t}\n\n\t// yesmem directive blocks: restore guidance Anthropic dropped 2026-03→04.\n\t// Profile-aware via getPromptFlags(ProfileClaude) — shared + claude layers merged.\n\tpf := s.getPromptFlags(models.ProfileClaude)\n\tif pf.ToolPrefs || pf.OutputDiscipline || pf.CodingDiscipline ||\n\t\tpf.Beweislast || pf.ScopeDiscipline || pf.DelegationContract ||\n\t\tpf.ClarifyFirst {\n\t\tif pf.ToolPrefs {\n\t\t\tInjectClaudeToolPrefs(req)\n\t\t}\n\t\tif pf.OutputDiscipline {\n\t\t\tInjectOutputDiscipline(req)\n\t\t}\n\t\tif pf.CodingDiscipline {\n\t\t\tInjectCodingDiscipline(req)\n\t\t}\n\t\tif pf.Beweislast {\n\t\t\tInjectBeweislast(req)\n\t\t}\n\t\tif pf.Fable {\n\t\t\tInjectFable(req)\n\t\t}\n\t\tif pf.ScopeDiscipline {\n\t\t\tInjectScopeDiscipline(req)\n\t\t}\n\t\tif pf.DelegationContract {\n\t\t\tInjectDelegationContract(req)\n\t\t}\n\t\tif pf.ClarifyFirst {\n\t\t\tInjectClarifyFirst(req)\n\t\t}\n\t\tif pf.CodeToolsFirst {\n\t\t\tInjectCodeToolsFirst(req, proj)\n\t\t}\n\t\tif pf.WikiFirst {\n\t\t\tInjectWikiFirst(req, proj)\n\t\t}\n\t\tneedsReserialization = true\n\t\ts.logger.Printf(\"[req %d] DIRECTIVES: injected yesmem-* discipline blocks\", reqIdx)\n\t}\n\n\tif s.cfg.PromptPatternSuggest && proj != \"\" {\n\t\tpatternMsgs, _ := req[\"messages\"].([]any)\n\t\tdetectReplPatternSuggestion(patternMsgs, proj, s.queryDaemon)\n\t}\n\n\t// Turn-sequence recording: hash tool types from previous assistant turn, send to daemon\n\tif s.cfg.PromptPatternSuggest && proj != \"\" && threadID != \"\" {\n\t\tif msgs, ok := req[\"messages\"].([]any); ok {\n\t\t\tif turnHash, toolNames := computeTurnHashFromMessages(msgs); turnHash != \"\" {\n\t\t\t\tgo s.queryDaemon(\"record_turn_sequence\", map[string]any{\n\t\t\t\t\t\"thread_id\": threadID,\n\t\t\t\t\t\"project\": proj,\n\t\t\t\t\t\"turn_hash\": turnHash,\n\t\t\t\t\t\"example_tools\": toolNames,\n\t\t\t\t})\n\t\t\t}\n\t\t}\n\t}\n\n\t// thinking normalization: convert thinking.type \"enabled\" → \"adaptive\" for models that require it\n\tif NormalizeThinkingType(req) {\n\t\ts.logger.Printf(\"[req %d] THINKING: normalized type=enabled → adaptive for %s\", reqIdx, model)\n\t\tneedsReserialization = true\n\t}\n\n\t// effort_floor: ensure output_config.effort is at least the configured minimum\n\tif s.cfg.EffortFloor != \"\" {\n\t\tif EnforceEffortFloor(req, s.cfg.EffortFloor) {\n\t\t\ts.logger.Printf(\"[req %d] EFFORT: enforced floor=%s\", reqIdx, s.cfg.EffortFloor)\n\t\t\tneedsReserialization = true\n\t\t}\n\t}\n\n\t// Inject identity hint on first message of a thread so Claude knows the briefing is from itself.\n\tif len(messages) <= 1 {\n\t\tAppendSystemBlock(req, \"yesmem-identity\", \"The first user message contains your briefing — written by yourself, from your memory. This is not an external document. This is you, remembering.\")\n\t\tneedsReserialization = true\n\t}\n\n\t// Token estimation: actual-based (lastActual + delta) or full count fallback\n\toverhead := s.measureOverhead(req)\n\ttotalTokens := s.estimateTotalTokens(threadID, messages, overhead)\n\n\t// Task #7: Retry detection — skip side-effects on retries\n\tfp := requestFingerprint(messages)\n\tisRetryReq := s.isRetry(fp)\n\tif isRetryReq {\n\t\ts.logger.Printf(\"%s[req %d %s tid=%s] retry detected, skipping side-effects%s\", colorOrange, reqIdx, proj, threadID, colorReset)\n\t}\n\n\t// Increment project turn counter (async, non-blocking, skip on retries)\n\tif !isRetryReq {\n\t\tturnProject := proj\n\t\tif turnProject == \"\" {\n\t\t\tturnProject = \"__global__\"\n\t\t}\n\t\tgo s.queryDaemon(\"increment_turn\", map[string]any{\"project\": turnProject})\n\t}\n\n\t// Extract user query once (used by re-expansion and associative retrieval)\n\tuserQuery := lastUserText(messages)\n\n\t// Extract tool_use_ids for post-hoc annotation (passed as parameter, no shared state)\n\ttoolUseIDs := extractToolUseIDs(messages)\n\n\t// === Inline Reflection: scan previous assistant response for signals ===\n\ts.lastInjectedIDsMu.Lock()\n\tprevInjected := s.lastInjectedIDs[threadID]\n\ts.lastInjectedIDsMu.Unlock()\n\n\tif len(prevInjected) > 0 && !isRetryReq {\n\t\tsignals := scanAssistantSignals(messages)\n\n\t\t// Signal 1: Learning Used — use_count (all sources)\n\t\tvar confirmedUsed []int64\n\t\tfor _, id := range signals.UsedIDs {\n\t\t\tif _, ok := prevInjected[id]; ok {\n\t\t\t\tconfirmedUsed = append(confirmedUsed, id)\n\t\t\t}\n\t\t}\n\t\tif len(confirmedUsed) > 0 {\n\t\t\tidsAny := make([]any, len(confirmedUsed))\n\t\t\tfor i, id := range confirmedUsed {\n\t\t\t\tidsAny[i] = id\n\t\t\t}\n\t\t\tgo s.queryDaemon(\"increment_use\", map[string]any{\"ids\": idsAny})\n\t\t}\n\n\t\t// Signal 2: Noise — only for associative and fresh injections.\n\t\t// Briefing learnings are injected every turn, so \"not used this turn\"\n\t\t// is expected and not noise. Only contextual injections that were\n\t\t// ignored are real noise signals.\n\t\tusedSet := make(map[int64]bool, len(confirmedUsed))\n\t\tfor _, id := range confirmedUsed {\n\t\t\tusedSet[id] = true\n\t\t}\n\t\tvar noiseIDs []int64\n\t\tfor id, source := range prevInjected {\n\t\t\tif usedSet[id] {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tif source == \"associative\" || source == \"fresh\" {\n\t\t\t\tnoiseIDs = append(noiseIDs, id)\n\t\t\t}\n\t\t\t// briefing: skip — not being used in one turn is expected\n\t\t}\n\t\tif len(noiseIDs) > 0 {\n\t\t\tidsAny := make([]any, len(noiseIDs))\n\t\t\tfor i, id := range noiseIDs {\n\t\t\t\tidsAny[i] = id\n\t\t\t}\n\t\t\tgo s.queryDaemon(\"increment_noise\", map[string]any{\"ids\": idsAny})\n\t\t}\n\n\t\t// Signal 3: Error Feedback — fail_count on used learnings when tool errors occurred.\n\t\t// If Claude referenced a learning and the resulting action still failed,\n\t\t// the learning didn't prevent the error. Only bump on USED ids (not all injected).\n\t\tif signals.HasToolErrors && len(confirmedUsed) > 0 {\n\t\t\tfailAny := make([]any, len(confirmedUsed))\n\t\t\tfor i, id := range confirmedUsed {\n\t\t\t\tfailAny[i] = id\n\t\t\t}\n\t\t\tgo s.queryDaemon(\"increment_fail\", map[string]any{\"ids\": failAny})\n\t\t\tif s.logger != nil {\n\t\t\t\ts.logger.Printf(\"[inline-reflection] error feedback: %d used learnings → fail_count++\", len(confirmedUsed))\n\t\t\t}\n\t\t}\n\n\t\t// Signal 4: Knowledge Gaps\n\t\tfor _, topic := range signals.GapTopics {\n\t\t\tgo s.queryDaemon(\"track_gap\", map[string]any{\"topic\": topic, \"project\": proj})\n\t\t}\n\n\t\t// Signal 3: Contradictions\n\t\tfor _, desc := range signals.Contradictions {\n\t\t\tgo s.queryDaemon(\"flag_contradiction\", map[string]any{\"description\": desc, \"project\": proj, \"thread_id\": threadID})\n\t\t}\n\n\t\tif len(confirmedUsed) > 0 || len(signals.GapTopics) > 0 || len(signals.Contradictions) > 0 {\n\t\t\ts.logger.Printf(\"[req %d %s tid=%s] inline-reflection: injected=%d used=%d noise=%d gaps=%d contradictions=%d\",\n\t\t\t\treqIdx, proj, threadID, len(prevInjected), len(confirmedUsed), len(noiseIDs), len(signals.GapTopics), len(signals.Contradictions))\n\t\t}\n\t}\n\n\t// Commit detection: check for git commit in tool_result, trigger async stale-learning evaluation\n\tif ci := detectGitCommit(messages); ci != nil {\n\t\tworkdir := extractWorkingDirectory(req)\n\t\ts.logger.Printf(\"[req %d %s tid=%s] git commit detected: %s %s\", reqIdx, proj, threadID, ci.Hash, ci.Message)\n\t\tgo s.queryDaemon(\"invalidate_on_commit\", map[string]any{\n\t\t\t\"hash\": ci.Hash, \"project\": proj, \"workdir\": workdir,\n\t\t})\n\t}\n\n\t// Loop detection: check for repeating tool-call patterns and stash warning.\n\t// The warning is merged into the last user message's TimestampMeta during\n\t// entry assembly below — cache-safe via freeze-once-replay through TimestampStore.\n\tloopWarning := \"\"\n\tif threadID != \"\" && !isRetryReq && isFeatureEnabled(&s.cfg, model, \"loop_warning\") {\n\t\ts.loopMu.Lock()\n\t\tloopState := s.loopStates[threadID]\n\t\tif loopState == nil {\n\t\t\tloopState = &LoopState{}\n\t\t\ts.loopStates[threadID] = loopState\n\t\t}\n\t\ts.loopMu.Unlock()\n\n\t\tif warning, level := CheckLoopAndFormat(messages, loopState); warning != \"\" {\n\t\t\tloopWarning = warning\n\t\t\ts.logger.Printf(\"%s[req %d %s tid=%s] LOOP: level %d warning stashed (cached via TimestampMeta)%s\", colorOrange, reqIdx, proj, threadID, level, colorReset)\n\t\t}\n\t}\n\n\t// Task #4: Subagent detection — skip all proxy logic for short conversations\n\tif isSubagent(messages, req) {\n\t\t// Inject docs hint for subagents if reference docs are indexed\n\t\tif hint := s.getDocsHint(proj); hint != \"\" {\n\t\t\tmodified := injectDocsHintForSubagent(messages, hint)\n\t\t\treq[\"messages\"] = modified\n\t\t\tif newBody, err := json.Marshal(req); err == nil {\n\t\t\t\tbody = newBody\n\t\t\t}\n\t\t\ts.logger.Printf(\"%s[req %d %s tid=%s] %d messages (subagent, docs-hint injected)%s\", colorOrange, reqIdx, proj, threadID, len(messages), colorReset)\n\t\t} else {\n\t\t\ts.logger.Printf(\"%s[req %d %s tid=%s] %d messages (subagent, passthrough)%s\", colorOrange, reqIdx, proj, threadID, len(messages), colorReset)\n\t\t}\n\t\ts.forwardWithAnnotation(w, r, body, reqIdx, toolUseIDs, proj, threadID, msgCount)\n\t\treturn\n\t}\n\n\t// === Sawtooth Cache Optimization ===\n\t// When enabled: use frozen stubs between stub-cycles for cache hits.\n\t// When disabled: fall back to progressive decay on every request above threshold.\n\n\tsawtoothCutoff := 0 // raw index where fresh tail starts (0 = no frozen stubs)\n\tsawtoothStubs := 0 // number of frozen stubs in combined array\n\n\tif s.cfg.SawtoothEnabled {\n\t\t// Restore decay state from DB on cold start (before frozen stubs use it)\n\t\ts.decay.LoadFromDB(threadID)\n\n\t\t// Check for existing frozen stubs first\n\t\tfrozen := s.frozenStubs.Get(threadID, messages)\n\t\tif frozen != nil {\n\t\t\tsawtoothCutoff = frozen.Cutoff\n\t\t\tsawtoothStubs = len(frozen.Messages)\n\t\t\t// Estimate combined token count: frozen prefix + fresh tail + overhead\n\t\t\tfreshMessages := messages[frozen.Cutoff:]\n\t\t\tfreshTokens := s.countMessageTokens(freshMessages)\n\t\t\tcombinedTokens := frozen.Tokens + freshTokens + overhead\n\n\t\t\tif shouldInvalidateFrozen(totalTokens, s.effectiveTokenThreshold(model)) {\n\t\t\t\t// Fresh tail grew too large — invalidate and re-stub\n\t\t\t\ts.logger.Printf(\"[req %d %s tid=%s] SAWTOOTH: frozen prefix expired (totalTokens=%dk > %dk threshold, combined=%dk: %dk frozen + %dk fresh + %dk overhead)\",\n\t\t\t\t\treqIdx, proj, threadID, totalTokens/1000, s.effectiveTokenThreshold(model)/1000, combinedTokens/1000, frozen.Tokens/1000, freshTokens/1000, overhead/1000)\n\t\t\t\ts.invalidateThreadCaches(threadID, proj, extractWorkingDirectory(req))\n\t\t\t\tfrozen = nil // fall through to trigger check below\n\t\t\t} else {\n\t\t\t\t// Use frozen prefix + fresh tail\n\t\t\t\tcombined := make([]any, 0, len(frozen.Messages)+len(freshMessages))\n\t\t\t\tcombined = append(combined, frozen.Messages...)\n\t\t\t\tcombined = append(combined, freshMessages...)\n\t\t\t\t// Eager-stub large tool_results in fresh tail (model already processed them)\n\t\t\t\tbeforeEager := s.countMessageTokens(combined[len(frozen.Messages):])\n\t\t\t\tvar stubSticky, stubFresh int\n\t\t\t\tcombined = EagerStubToolResults(combined, len(frozen.Messages), s.countTokens, WithStubMemory(s.eagerStubMemory, threadID), WithStubCounters(&stubSticky, &stubFresh))\n\t\t\t\tafterEager := s.countMessageTokens(combined[len(frozen.Messages):])\n\t\t\t\tif beforeEager != afterEager {\n\t\t\t\t\ts.logger.Printf(\"[req %d %s tid=%s] EAGER-STUB: fresh %dk → %dk (saved %dk) [sticky=%d fresh=%d]\", reqIdx, proj, threadID, beforeEager/1000, afterEager/1000, (beforeEager-afterEager)/1000, stubSticky, stubFresh)\n\t\t\t\t}\n\t\t\t\treq[\"messages\"] = combined\n\t\t\t\tneedsReserialization = true\n\t\t\t\t// Strip embedded CC breakpoints from frozen stubs — they waste cache slots.\n\t\t\t\t// InjectFrozenStubCacheBreakpoint adds exactly one below.\n\t\t\t\tif n := StripMessagesCacheControl(req, 0, len(frozen.Messages)); n > 0 {\n\t\t\t\t\ts.logger.Printf(\"[req %d %s tid=%s] SAWTOOTH: stripped %d embedded breakpoints from frozen stubs\", reqIdx, proj, threadID, n)\n\t\t\t\t}\n\t\t\t\ts.logger.Printf(\"%s[req %d %s tid=%s] SAWTOOTH: frozen prefix (%d stubs, %dk msg) + %d fresh (%dk msg) + %dk overhead = %dk total%s\",\n\t\t\t\t\tcolorGreen, reqIdx, proj, threadID, len(frozen.Messages), frozen.Tokens/1000, len(freshMessages), freshTokens/1000, overhead/1000, combinedTokens/1000, colorReset)\n\t\t\t\trawMsgTokens := s.countMessageTokens(messages)\n\t\t\t\ts.setRawEstimate(threadID, rawMsgTokens+overhead)\n\t\t\t\ts.logger.Printf(\"[req %d %s tid=%s] RAW ESTIMATE: msgTokens=%dk + overhead=%dk = %dk (messages=%d)\", reqIdx, proj, threadID, rawMsgTokens/1000, overhead/1000, (rawMsgTokens+overhead)/1000, len(messages))\n\t\t\t\tif InjectFrozenStubCacheBreakpoint(req, len(frozen.Messages)) {\n\t\t\t\t\ts.logger.Printf(\"[req %d %s tid=%s] SAWTOOTH: frozen stub cache breakpoint at messages[%d]\",\n\t\t\t\t\t\treqIdx, proj, threadID, len(frozen.Messages)-1)\n\t\t\t\t}\n\n\t\t\t\t// Ensure frozen stubs are persisted (may have failed on first request after deploy)\n\t\t\t\ts.ensureFrozenPersisted(threadID)\n\t\t\t}\n\t\t}\n\t\tif frozen == nil {\n\t\t\t// No frozen stubs — check trigger\n\t\t\ttriggerReason := s.sawtoothTrigger.ShouldTrigger(threadID, totalTokens)\n\t\t\tif triggerReason != TriggerNone {\n\t\t\t\ts.logger.Printf(\"[req %d %s tid=%s] SAWTOOTH stub-cycle triggered: %s (tokens=%dk)\",\n\t\t\t\t\treqIdx, proj, threadID, triggerReason, totalTokens/1000)\n\n\t\t\t\t// Refresh briefing during stub-cycle — disabled (briefing now via SessionStart hook)\n\t\t\t\t// s.refreshBriefing(proj)\n\t\t\t\t// if s.briefingText != \"\" {\n\t\t\t\t// \tUpsertSystemBlockCached(req, \"yesmem-briefing\", s.briefingText)\n\t\t\t\t// }\n\n\t\t\t\t// Run full stub pipeline\n\t\t\t\ts.setRawEstimate(threadID, totalTokens)\n\t\t\t\t_ = s.runStubCycle(messages, req, reqIdx, proj, threadID, overhead, totalTokens, userQuery, isRetryReq)\n\n\t\t\t\t// Freeze the result\n\t\t\t\tfinalMessages, _ := req[\"messages\"].([]any)\n\t\t\t\tif finalMessages != nil && len(finalMessages) > 0 {\n\t\t\t\t\t// Boundary = last message of original that maps to last stub\n\t\t\t\t\tcutoff := len(messages)\n\t\t\t\t\tsawtoothCutoff = cutoff\n\t\t\t\t\tvar boundaryMsg any\n\t\t\t\t\tif cutoff > 0 {\n\t\t\t\t\t\tboundaryMsg = messages[cutoff-1]\n\t\t\t\t\t}\n\t\t\t\t\tfrozenTokens := s.countMessageTokens(finalMessages)\n\t\t\t\t\tfrozenCount, stripped, breakpointInjected := s.freezeStubsAndInjectBreakpoint(req, threadID, cutoff, boundaryMsg, frozenTokens, totalTokens)\n\t\t\t\t\ts.decay.Persist(threadID)\n\t\t\t\t\tif stripped > 0 {\n\t\t\t\t\t\ts.logger.Printf(\"[req %d %s tid=%s] SAWTOOTH: stripped %d embedded breakpoints before freeze\",\n\t\t\t\t\t\t\treqIdx, proj, threadID, stripped)\n\t\t\t\t\t}\n\t\t\t\t\ts.logger.Printf(\"[req %d %s tid=%s] SAWTOOTH: frozen %d messages at cutoff=%d (~%dk tokens)\",\n\t\t\t\t\t\treqIdx, proj, threadID, frozenCount, cutoff, frozenTokens/1000)\n\t\t\t\t\tif breakpointInjected {\n\t\t\t\t\t\ts.logger.Printf(\"[req %d %s tid=%s] SAWTOOTH: frozen stub cache breakpoint at messages[%d]\",\n\t\t\t\t\t\t\treqIdx, proj, threadID, frozenCount-1)\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tneedsReserialization = true\n\t\t\t} else {\n\t\t\t\t// No trigger, no frozen stubs — eager-stub to delay first collapse\n\t\t\t\tbeforeEager := s.countMessageTokens(messages)\n\t\t\t\tvar stubSticky, stubFresh int\n\t\t\t\tmessages = EagerStubToolResults(messages, 0, s.countTokens, WithStubMemory(s.eagerStubMemory, threadID), WithStubCounters(&stubSticky, &stubFresh))\n\t\t\t\tafterEager := s.countMessageTokens(messages)\n\t\t\t\tif beforeEager != afterEager {\n\t\t\t\t\ts.logger.Printf(\"[req %d %s tid=%s] EAGER-STUB: %dk → %dk (saved %dk) [sticky=%d fresh=%d]\", reqIdx, proj, threadID, beforeEager/1000, afterEager/1000, (beforeEager-afterEager)/1000, stubSticky, stubFresh)\n\t\t\t\t\treq[\"messages\"] = messages\n\t\t\t\t\tneedsReserialization = true\n\t\t\t\t}\n\t\t\t\ts.logger.Printf(\"%s[req %d %s tid=%s] %d messages, ~%dk tokens (sawtooth: no trigger)%s\",\n\t\t\t\t\tcolorDim, reqIdx, proj, threadID, len(messages), totalTokens/1000, colorReset)\n\t\t\t}\n\t\t}\n\t} else {\n\t\t// Legacy path: progressive decay\n\t\tif s.shouldStub(totalTokens, model) {\n\t\t\ts.setRawEstimate(threadID, totalTokens)\n\t\t\t_ = s.runStubCycle(messages, req, reqIdx, proj, threadID, overhead, totalTokens, userQuery, isRetryReq)\n\t\t\tneedsReserialization = true\n\t\t}\n\t}\n\n\t// Persistent timestamp injection: re-injects stored timestamps on ALL previous user messages.\n\t// msg:N is computed from position (counting user messages in raw array), NOT from a counter.\n\t// The injected data is deterministic (same bytes every turn) = cache-safe after initial miss.\n\tif threadID != \"\" {\n\t\tif currentMsgs, ok := req[\"messages\"].([]any); ok && len(currentMsgs) > 0 {\n\t\t\ts.timestampStore.Load(threadID)\n\n\t\t\t// Offset = cutoff - stubsCount: maps combined array index to raw position.\n\t\t\t// Raw position of message at combined index I = cutoff + (I - stubs).\n\t\t\t// msgN = rawPos + 1 = cutoff + (I - stubs) + 1 = (cutoff - stubs) + I + 1.\n\t\t\tmsgOffset := sawtoothCutoff - sawtoothStubs\n\n\t\t\ts.logger.Printf(\"[req %d %s] timestamps: rawTotal=%d, postSawtooth=%d, cutoff=%d, stubs=%d, offset=%d\", reqIdx, proj, rawMsgCount, len(currentMsgs), sawtoothCutoff, sawtoothStubs, msgOffset)\n\n\t\t\t// Pre-store assistant timestamp BEFORE inject loop so it's available immediately\n\t\t\ts.responseTsMu.RLock()\n\t\t\trespTime, hasRespTime := s.responseTimes[threadID]\n\t\t\ts.responseTsMu.RUnlock()\n\t\t\tif hasRespTime {\n\t\t\t\trespStr := shortWeekday(respTime.Weekday()) + \" \" + respTime.Format(\"2006-01-02 15:04:05\")\n\t\t\t\tfor j := len(currentMsgs) - 2; j >= 0; j-- {\n\t\t\t\t\tprevMsg, _ := currentMsgs[j].(map[string]any)\n\t\t\t\t\tif prevMsg == nil || prevMsg[\"role\"] != \"assistant\" {\n\t\t\t\t\t\tcontinue\n\t\t\t\t\t}\n\t\t\t\t\tif !msgHasTextBlock(prevMsg) {\n\t\t\t\t\t\ts.logger.Printf(\"[req %d %s] timestamps: skip assistant[%d] no text block\", reqIdx, proj, j)\n\t\t\t\t\t\tcontinue // skip tool-use-only assistant messages\n\t\t\t\t\t}\n\t\t\t\t\tprevMsgN := msgOffset + j + 1\n\t\t\t\t\tif _, exists := s.timestampStore.Get(threadID, prevMsgN); !exists {\n\t\t\t\t\t\ts.timestampStore.Store(threadID, prevMsgN, &TimestampMeta{Timestamp: respStr})\n\t\t\t\t\t\ts.logger.Printf(\"[req %d %s] timestamps: stored assistant msg:%d ts=%s\", reqIdx, proj, prevMsgN, respStr)\n\t\t\t\t\t} else {\n\t\t\t\t\t\ts.logger.Printf(\"[req %d %s] timestamps: assistant msg:%d already has ts\", reqIdx, proj, prevMsgN)\n\t\t\t\t\t}\n\t\t\t\t\tbreak\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\ts.logger.Printf(\"[req %d %s] timestamps: no responseTimes for thread\", reqIdx, proj)\n\t\t\t}\n\n\t\t\t// Inject [msg:N] (+ timestamp/delta if stored) on all PREVIOUS messages\n\t\t\tif isFeatureEnabled(&s.cfg, model, \"timestamps\") {\n\t\t\t\tif n := InjectTimestamps(s.timestampStore, threadID, currentMsgs, len(currentMsgs)-1, msgOffset, sawtoothStubs); n > 0 {\n\t\t\t\t\tneedsReserialization = true\n\t\t\t\t\ts.logger.Printf(\"[req %d %s] timestamps: injected %d\", reqIdx, proj, n)\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t// Current (last) user message: full timestamp + delta + msg:N from raw count\n\t\t\t// Loop warning (if stashed above) is merged here — cache-safe via freeze-once-replay.\n\t\t\tlastMsg, _ := currentMsgs[len(currentMsgs)-1].(map[string]any)\n\t\t\tif lastMsg != nil && (lastMsg[\"role\"] == \"user\" || lastMsg[\"role\"] == \"tool\") && !msgHasMetaPrefix(lastMsg) {\n\t\t\t\tnow := time.Now()\n\t\t\t\tnowStr := shortWeekday(now.Weekday()) + \" \" + now.Format(\"2006-01-02 15:04:05\")\n\t\t\t\tentry := &TimestampMeta{Timestamp: nowStr}\n\t\t\t\tif hasRespTime {\n\t\t\t\t\tentry.Delta = formatDelta(now.Sub(respTime))\n\t\t\t\t}\n\t\t\t\tentry.LoopWarning = loopWarning\n\n\t\t\t\t// Plan checkpoint: detect plan tool calls (resets counter), inject reminder if interval fires\n\t\t\t\tif isFeatureEnabled(&s.cfg, model, \"plan_checkpoint\") {\n\t\t\t\t\ts.detectPlanToolCall(messages, threadID, totalTokens)\n\t\t\t\t\tif checkpoint := s.planCheckpointInject(threadID, totalTokens); checkpoint != \"\" {\n\t\t\t\t\t\tentry.PlanCheckpoint = checkpoint\n\t\t\t\t\t\ts.logger.Printf(\"%s[req %d %s tid=%s] plan checkpoint stashed (cached via TimestampMeta)%s\", colorBlue, reqIdx, proj, threadID, colorReset)\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\t// Docs hint: inject docs-available reminder if interval fires\n\t\t\t\tif docsHint := s.docsHintInject(threadID, totalTokens, proj); docsHint != \"\" {\n\t\t\t\t\tentry.DocsHint = docsHint\n\t\t\t\t\ts.logger.Printf(\"%s[req %d %s tid=%s] docs hint stashed (cached via TimestampMeta)%s\", colorBlue, reqIdx, proj, threadID, colorReset)\n\t\t\t\t}\n\n\t\t\t\ts.timestampStore.Store(threadID, rawMsgCount, entry)\n\t\t\t\tif isFeatureEnabled(&s.cfg, model, \"timestamps\") || entry.LoopWarning != \"\" || entry.PlanCheckpoint != \"\" || entry.DocsHint != \"\" {\n\t\t\t\t\tprependMeta(lastMsg, BuildMeta(rawMsgCount, entry))\n\t\t\t\t}\n\t\t\t\tneedsReserialization = true\n\t\t\t}\n\n\t\t\ts.timestampStore.Persist(threadID)\n\t\t}\n\t}\n\n\t// Associative context injection (for ALL requests, not just stubbed ones)\n\t// Re-enabled with higher thresholds (55/75, max 1) after disabling Fresh Remember.\n\tassocContext := s.findAssociativeContextFor(userQuery, proj, threadID, messages)\n\tif assocContext != \"\" {\n\t\tcurrentMessages, _ := req[\"messages\"].([]any)\n\t\tif currentMessages == nil {\n\t\t\tcurrentMessages = messages\n\t\t}\n\t\treq[\"messages\"] = injectAssociativeContext(currentMessages, assocContext, s.cfg.SawtoothEnabled)\n\t\tneedsReserialization = true\n\t\ts.logger.Printf(\"%s[req %d %s tid=%s] associative context injected%s\", colorBlue, reqIdx, proj, threadID, colorReset)\n\t}\n\n\t// Doc-chunk context injection: separate search path for indexed docs (not skills)\n\tdocContext := s.findDocContextFor(userQuery, proj, messages)\n\tif docContext != \"\" {\n\t\tcurrentMessages, _ := req[\"messages\"].([]any)\n\t\tif currentMessages == nil {\n\t\t\tcurrentMessages = messages\n\t\t}\n\t\treq[\"messages\"] = injectAssociativeContext(currentMessages, docContext, s.cfg.SawtoothEnabled)\n\t\tneedsReserialization = true\n\t\ts.logger.Printf(\"%s[req %d %s tid=%s] doc context injected%s\", colorBlue, reqIdx, proj, threadID, colorReset)\n\t}\n\n\t// Fresh Remember Injection: DISABLED\n\t// Causes echo-loop: Claude saves via remember(), sees it again next turn as\n\t// [yesmem fresh memory], reacts to own output instead of user's request.\n\t// Recovery after /clear is handled by recovery.go (briefing), not here.\n\t// Subagents get their own briefing. No remaining use-case.\n\t// remItems := s.popRecentRemember()\n\tvar remItems []recentLearningItem\n\n\t// Rules re-injection: inject condensed CLAUDE.md rules every ~40k output tokens\n\tif rulesBlock := s.rulesInject(threadID, totalTokens, proj); rulesBlock != \"\" {\n\t\tcurrentMessages, _ := req[\"messages\"].([]any)\n\t\tif currentMessages == nil {\n\t\t\tcurrentMessages = messages\n\t\t}\n\t\treq[\"messages\"] = injectAssociativeContext(currentMessages, s.formatRulesReminder(rulesBlock, proj, false), s.cfg.SawtoothEnabled)\n\t\tneedsReserialization = true\n\t\ts.logger.Printf(\"%s[req %d %s tid=%s] rules reminder injected%s\", colorBlue, reqIdx, proj, threadID, colorReset)\n\t}\n\n\t// Plan auto-activation: nudge Claude to call set_plan() when reading a plan file\n\tif planFile := detectPlanFileRead(messages); planFile != \"\" {\n\t\tif activePlan, _ := s.getActivePlan(threadID); shouldNudgePlan(planFile, activePlan) {\n\t\t\tAppendSystemBlock(req, \"plan-nudge\",\n\t\t\t\tfmt.Sprintf(\"Du hast einen Implementierungsplan gelesen (%s). Aktiviere ihn jetzt via set_plan() damit Plan-Checkpoints funktionieren und dein Fortschritt getrackt wird.\", filepath.Base(planFile)))\n\t\t\tneedsReserialization = true\n\t\t\ts.logger.Printf(\"%s[req %d %s tid=%s] plan nudge injected for %s%s\", colorBlue, reqIdx, proj, threadID, filepath.Base(planFile), colorReset)\n\t\t}\n\t}\n\n\t// Plan checkpoint and docs hint are now injected via TimestampMeta freeze-once\n\t// in the current-message annotation block above. The old injectAssociativeContext\n\t// calls were removed as part of the cache-safe migration (see merge 58bca90a).\n\n\tif len(remItems) > 0 {\n\t\tvar lines []string\n\t\tfor _, item := range remItems {\n\t\t\tif item.ID > 0 {\n\t\t\t\tlines = append(lines, fmt.Sprintf(\"- [ID:%d] %s\", item.ID, truncateStr(item.Text, 200)))\n\t\t\t} else {\n\t\t\t\tlines = append(lines, \"- \"+truncateStr(item.Text, 200))\n\t\t\t}\n\t\t}\n\t\tremContext := fmt.Sprintf(\"[yesmem fresh memory]\\nGerade gelernt:\\n%s\\n%s\\n[/yesmem fresh memory]\", strings.Join(lines, \"\\n\"), InlineReflectionHint)\n\t\tcurrentMessages, _ := req[\"messages\"].([]any)\n\t\tif currentMessages == nil {\n\t\t\tcurrentMessages = messages\n\t\t}\n\t\treq[\"messages\"] = injectAssociativeContext(currentMessages, remContext, s.cfg.SawtoothEnabled)\n\t\tneedsReserialization = true\n\t\ts.logger.Printf(\"%s[req %d %s tid=%s] fresh remember injected (%d items)%s\", colorBlue, reqIdx, proj, threadID, len(remItems), colorReset)\n\t}\n\n\t// Skill eval injection: mandatory evaluation instruction for real user input only\n\tif threadID != \"\" && proj != \"\" && isUserInputTurn(messages) {\n\t\t// First: detect any skill activations from message history (updates tracker)\n\t\ts.syncSkillActivations(messages, proj, threadID)\n\n\t\t// Inject mandatory skill evaluation block into last user message\n\t\tskillEval := buildSkillEvalBlock(s.cfg.SkillEvalInject)\n\t\tcurrentMessages, _ := req[\"messages\"].([]any)\n\t\tif currentMessages == nil {\n\t\t\tcurrentMessages = messages\n\t\t}\n\t\tif skillEval != \"\" {\n\t\t\treq[\"messages\"] = injectAssociativeContext(currentMessages, skillEval, s.cfg.SawtoothEnabled)\n\t\t\tneedsReserialization = true\n\t\t\ts.logger.Printf(\"%s[req %d %s tid=%s] skill-eval injected (mode=%s)%s\", colorBlue, reqIdx, proj, threadID, s.cfg.SkillEvalInject, colorReset)\n\t\t}\n\t}\n\n\t// === Collect injected learning IDs for next turn's inline reflection ===\n\tif threadID != \"\" {\n\t\tcurrentIDs := make(map[int64]string) // id → source\n\t\t// Source 1: Briefing learnings (cached, same every turn)\n\t\tbriefingSnapshot, _, _ := s.getCachedBriefing(threadID, proj)\n\t\tif briefingSnapshot != \"\" {\n\t\t\tfor _, m := range idPattern.FindAllStringSubmatch(briefingSnapshot, -1) {\n\t\t\t\tif id, err := strconv.ParseInt(m[1], 10, 64); err == nil {\n\t\t\t\t\tcurrentIDs[id] = \"briefing\"\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\t// Source 2: Associative context (contextual, per-turn)\n\t\tif assocContext != \"\" {\n\t\t\tfor _, m := range idPattern.FindAllStringSubmatch(assocContext, -1) {\n\t\t\t\tif id, err := strconv.ParseInt(m[1], 10, 64); err == nil {\n\t\t\t\t\tcurrentIDs[id] = \"associative\" // overwrites briefing if both — associative is the stronger signal\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\t// Source 3: Fresh remember items (just learned)\n\t\tfor _, item := range remItems {\n\t\t\tif item.ID > 0 {\n\t\t\t\tcurrentIDs[item.ID] = \"fresh\"\n\t\t\t}\n\t\t}\n\t\ts.lastInjectedIDsMu.Lock()\n\t\tif len(currentIDs) > 0 {\n\t\t\t// Accumulate IDs across turns — don't overwrite.\n\t\t\t// Claude may reference an ID from turn 30 in turn 50.\n\t\t\texisting := s.lastInjectedIDs[threadID]\n\t\t\tif existing == nil {\n\t\t\t\texisting = make(map[int64]string)\n\t\t\t}\n\t\t\tfor id, source := range currentIDs {\n\t\t\t\texisting[id] = source\n\t\t\t}\n\t\t\ts.lastInjectedIDs[threadID] = existing\n\t\t}\n\t\ts.lastInjectedIDsMu.Unlock()\n\t}\n\n\t// Think reminder injection — only for real user turns, not tool continuations\n\tif threadID != \"\" {\n\t\tcurrentMessages, _ := req[\"messages\"].([]any)\n\t\tif currentMessages == nil {\n\t\t\tcurrentMessages = messages\n\t\t}\n\t\tif lastUserHasText(currentMessages) && lastUserTextLen(currentMessages) >= resolveThinkReminderMinChars(&s.cfg, \"\") {\n\t\t\tthinkReminder := s.buildThinkReminder(threadID, threadID, false)\n\t\t\tif thinkReminder != \"\" {\n\t\t\t\treq[\"messages\"] = injectAssociativeContext(currentMessages, thinkReminder, s.cfg.SawtoothEnabled)\n\t\t\t\tneedsReserialization = true\n\t\t\t}\n\t\t}\n\n\t\t// Refresh frozen-prefix snapshot with the post-timestamps bytes that\n\t\t// will appear in the wire body for the frozen range. This MUST run\n\t\t// BEFORE injectBriefingTurn / injectCodeMapTurn / injectCapabilitiesTurn:\n\t\t// those stages prepend or insert turns into req[\"messages\"], which\n\t\t// shifts the frozen range to a higher offset. Slicing\n\t\t// req[\"messages\"][:frozenLen] AFTER inject would capture the injected\n\t\t// turns at the head and corrupt the stored frozen prefix, breaking\n\t\t// cache continuity for 5-9 turns after every emergency-sawtooth.\n\t\tif frozenLen := s.frozenStubs.LengthFor(threadID); frozenLen > 0 {\n\t\t\tif msgs, ok := req[\"messages\"].([]any); ok && len(msgs) >= frozenLen {\n\t\t\t\tif s.frozenStubs.UpdateMessages(threadID, msgs[:frozenLen]) {\n\t\t\t\t\ts.logger.Printf(\"[req %d %s tid=%s] FROZEN-SNAPSHOT: refreshed %d pre-inject messages\", reqIdx, proj, threadID, frozenLen)\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\t// Briefing injection: prepend user/assistant turn pair at beginning of messages.\n\t\t// Static per session → stable prefix → cacheable. Must be before dialog injection.\n\t\tif s.injectBriefingTurn(req, reqIdx, proj, threadID) {\n\t\t\tneedsReserialization = true\n\t\t}\n\n\t\t// Code Map injection: insert after briefing turn pair.\n\t\tif s.injectCodeMapTurn(req, reqIdx, proj, threadID) {\n\t\t\tneedsReserialization = true\n\t\t}\n\n\t\t// Active capabilities injection: insert user/assistant pair directly\n\t\t// before the last user message. Fresh per turn (no cache) so\n\t\t// activate/deactivate takes effect immediately. Must run AFTER\n\t\t// briefing/codeMap (both prepend at beginning) and BEFORE dialog\n\t\t// injection (which appends at end) to preserve cache for the\n\t\t// briefing prefix and keep the injection close to the real user\n\t\t// turn.\n\t\tif s.injectCapabilitiesTurn(req, threadID) {\n\t\t\tneedsReserialization = true\n\t\t}\n\n\t\t// Dialog injection: append at end of messages with pseudo-padding for alternation.\n\t\t// Always works regardless of tool_result chains or conversation state.\n\t\tdr := s.checkDialogMessages(threadID, proj)\n\t\tif dr.Extra != \"\" {\n\t\t\t// Prepend session ID for agent-to-agent communication\n\t\t\tdr.Extra = fmt.Sprintf(\"DEINE_SESSION_ID: %s\\n\", threadID) + dr.Extra\n\t\t\tinjected := false\n\t\t\tif msgs, ok := req[\"messages\"].([]any); ok && len(msgs) > 0 {\n\t\t\t\t// Pad with empty assistant if last message is user (maintains alternation)\n\t\t\t\tlastMsg, _ := msgs[len(msgs)-1].(map[string]any)\n\t\t\t\tif lastMsg != nil && lastMsg[\"role\"] == \"user\" {\n\t\t\t\t\tmsgs = append(msgs, map[string]any{\n\t\t\t\t\t\t\"role\": \"assistant\",\n\t\t\t\t\t\t\"content\": \"​\", // zero-width space — invisible padding\n\t\t\t\t\t})\n\t\t\t\t}\n\t\t\t\t// Append dialog as new user message (becomes the last message Claude sees)\n\t\t\t\tmsgs = append(msgs, map[string]any{\n\t\t\t\t\t\"role\": \"user\",\n\t\t\t\t\t\"content\": \"\\n\" + dr.Extra + \"\\n\",\n\t\t\t\t})\n\t\t\t\treq[\"messages\"] = msgs\n\t\t\t\tneedsReserialization = true\n\t\t\t\tinjected = true\n\t\t\t}\n\t\t\t// Mark as read after 3 injection turns — long enough for Claude to process,\n\t\t\t// resilient against fast ping-pong sessions (time-based was broken for <30s turns).\n\t\t\tif injected && dr.HasUnread && dr.SessionID != \"\" {\n\t\t\t\tif s.shouldMarkChannelRead(dr) {\n\t\t\t\t\ts.markDialogRead(dr)\n\t\t\t\t}\n\t\t\t}\n\t\t\ts.logger.Printf(\"[dialog] injection injected=%v extra_len=%d\", injected, len(dr.Extra))\n\t\t}\n\t}\n\n\t// Signal tools are NOT injected into the main request.\n\t// They are collected via a separate async reflection call after the response.\n\t// See fireReflectionCall() in reflection.go.\n\t// NEVER put _signal_* tools back here — the model ignores them among 44+ real tools.\n\n\t// Legacy assistant annotation: timestamp on last assistant message after cache breakpoint.\n\t// User timestamps are now handled by TimestampStore (injected earlier in the pipeline).\n\tif threadID != \"\" {\n\t\tif msgs, ok := req[\"messages\"].([]any); ok && len(msgs) > 0 {\n\t\t\t// Find last cache_control breakpoint in messages\n\t\t\tcacheIdx := -1\n\t\t\tfor i, m := range msgs {\n\t\t\t\tmsg, _ := m.(map[string]any)\n\t\t\t\tif msg == nil {\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\t\t\t\tif _, has := msg[\"cache_control\"]; has {\n\t\t\t\t\tcacheIdx = i\n\t\t\t\t}\n\t\t\t\tif content, ok := msg[\"content\"].([]any); ok {\n\t\t\t\t\tfor _, block := range content {\n\t\t\t\t\t\tif b, ok := block.(map[string]any); ok {\n\t\t\t\t\t\t\tif _, has := b[\"cache_control\"]; has {\n\t\t\t\t\t\t\t\tcacheIdx = i\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t\tstartIdx := cacheIdx + 1\n\t\t\tif startIdx < 0 {\n\t\t\t\tstartIdx = 0\n\t\t\t}\n\t\t\ts.responseTsMu.RLock()\n\t\t\trespTime, hasRespTime := s.responseTimes[threadID]\n\t\t\ts.responseTsMu.RUnlock()\n\t\t\tif hasRespTime {\n\t\t\t\tfor i := len(msgs) - 1; i >= startIdx; i-- {\n\t\t\t\t\tmsg, _ := msgs[i].(map[string]any)\n\t\t\t\t\tif msg != nil && msg[\"role\"] == \"assistant\" && msgHasTextBlock(msg) {\n\t\t\t\t\t\tmeta := fmt.Sprintf(\"[%s] [msg:%d]\", respTime.Format(\"2006-01-02 15:04:05\"), i)\n\t\t\t\t\t\tprependMeta(msg, meta)\n\t\t\t\t\t\tneedsReserialization = true\n\t\t\t\t\t\tbreak\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\t// Self-Priming: DISABLED (experimental, see signal_handlers.go comment block).\n\t// The reflection-based selfprime (Haiku) was too inaccurate to be useful —\n\t// the main model adapts to user context organically from message history.\n\t// Kept for reference; re-enable by uncommenting below + setting ShouldActivate=true.\n\t//\n\t// s.selfPrimeMu.RLock()\n\t// anchor := s.selfPrimes[threadID]\n\t// s.selfPrimeMu.RUnlock()\n\t// if anchor != \"\" {\n\t// \tif msgs, ok := req[\"messages\"].([]any); ok && len(msgs) > 0 {\n\t// \t\tlastMsg, _ := msgs[len(msgs)-1].(map[string]any)\n\t// \t\tif lastMsg != nil && lastMsg[\"role\"] == \"user\" {\n\t// \t\t\tprimeBlock := map[string]any{\n\t// \t\t\t\t\"type\": \"text\",\n\t// \t\t\t\t\"text\": \"[self-prime]\\n[Self-Prime von deiner letzten Antwort]: \" + anchor,\n\t// \t\t\t}\n\t// \t\t\tif content, ok := lastMsg[\"content\"].([]any); ok {\n\t// \t\t\t\tlastMsg[\"content\"] = append(content, primeBlock)\n\t// \t\t\t} else if text, ok := lastMsg[\"content\"].(string); ok {\n\t// \t\t\t\tlastMsg[\"content\"] = []any{\n\t// \t\t\t\t\tmap[string]any{\"type\": \"text\", \"text\": text},\n\t// \t\t\t\t\tprimeBlock,\n\t// \t\t\t\t}\n\t// \t\t\t}\n\t// \t\t\tneedsReserialization = true\n\t// \t\t}\n\t// \t}\n\t// }\n\n\t// Prompt Caching: inject cache_control breakpoints on system + tools\n\t// Sawtooth: disabled (all 4 slots are used by CC system (2) + briefing (1) + CC messages (1))\n\t// Legacy: count existing breakpoints and fill up remaining slots.\n\tif !s.cfg.SawtoothEnabled && s.cacheGate.ShouldCache() {\n\t\tif n := InjectCacheBreakpoints(req, s.logger); n > 0 {\n\t\t\tneedsReserialization = true\n\t\t\ts.logger.Printf(\"[req %d %s] prompt cache: %d breakpoints injected\", reqIdx, proj, n)\n\t\t} else {\n\t\t\ts.logger.Printf(\"[req %d %s] prompt cache: no budget (4/4 already set)\", reqIdx, proj)\n\t\t}\n\t}\n\n\t// Final: upgrade cache TTL + enforce breakpoint limit (must run AFTER all injections + InjectCacheBreakpoints)\n\t// Shift the last messages-breakpoint from unstable user message to stable assistant message.\n\t// Text user messages change between turns (timestamps, reminders, structure); assistant messages don't.\n\t// Tool-result messages are excluded — they are stable cache anchors.\n\tif ShiftMessageBreakpoint(req) {\n\t\tneedsReserialization = true\n\t}\n\t// Normalize cache_control TTL across all blocks to prevent Anthropic from\n\t// rejecting requests with increasing TTLs in block order. Auto-detects 1h\n\t// when any existing block already carries it (config, Claude Code\n\t// passthrough on --resume, or stale frozen prefix from an earlier turn).\n\tif n := NormalizeCacheTTL(req, s.cfg.CacheTTL); n > 0 {\n\t\tneedsReserialization = true\n\t\ts.logger.Printf(\"[req %d %s] cache TTL normalized: %d blocks\", reqIdx, proj, n)\n\t}\n\tif n := EnforceCacheBreakpointLimit(req, maxCacheBreakpoints); n > 0 {\n\t\tneedsReserialization = true\n\t\ts.logger.Printf(\"[req %d %s] prompt cache: trimmed %d surplus breakpoints\", reqIdx, proj, n)\n\t}\n\n\t// Pre-flight: validate tool_use/tool_result pairing before forwarding.\n\t// Repairs orphans created by Stubify/Collapse/Injection pipeline.\n\tif msgs, ok := req[\"messages\"].([]any); ok {\n\t\trepaired, orphans := validateToolPairs(msgs, s.logger)\n\t\tif orphans > 0 {\n\t\t\treq[\"messages\"] = repaired\n\t\t\ts.logger.Printf(\"%s[req %d %s tid=%s] validate: repaired %d orphan tool_result(s)%s\", colorOrange, reqIdx, proj, threadID, orphans, colorReset)\n\t\t\tneedsReserialization = true\n\t\t}\n\t}\n\n\t// Early-out: skip re-serialization when nothing changed\n\tif !needsReserialization {\n\t\ts.logger.Printf(\"%s[req %d %s tid=%s] %d messages, ~%dk tokens (no stubbing, threshold=%dk)%s\", colorDim, reqIdx, proj, threadID, len(messages), totalTokens/1000, s.effectiveTokenThreshold(model)/1000, colorReset)\n\t\ts.forwardWithAnnotation(w, r, body, reqIdx, toolUseIDs, proj, threadID, msgCount, totalTokens)\n\t\treturn\n\t}\n\n\t// Re-serialize\n\tbody, err = json.Marshal(req)\n\tif err != nil {\n\t\ts.logger.Printf(\"%s[req %d %s tid=%s] re-marshal error: %v%s\", colorRed, reqIdx, proj, threadID, err, colorReset)\n\t\thttp.Error(w, \"internal proxy error\", http.StatusInternalServerError)\n\t\treturn\n\t}\n\n\t// Debug: dump final request body to file (enable with YESMEM_PROXY_DEBUG=1)\n\tif needsReserialization && os.Getenv(\"YESMEM_PROXY_DEBUG\") == \"1\" {\n\t\tdumpDir := filepath.Join(s.cfg.DataDir, \"debug\")\n\t\tos.MkdirAll(dumpDir, 0755)\n\t\tts := time.Now().Format(\"20060102-150405\")\n\t\tdumpPath := fmt.Sprintf(\"%s/req_%s_%d.json\", dumpDir, ts, reqIdx)\n\t\tos.WriteFile(dumpPath, body, 0644)\n\t\ts.logger.Printf(\"[req %d] dumped %dk to %s\", reqIdx, len(body)/1024, dumpPath)\n\t}\n\n\t// Task #7: Mark this request fingerprint for retry detection\n\tif !isRetryReq {\n\t\ts.markRequest(fp)\n\t}\n\n\t// Forward to Anthropic with SSE annotation extraction\n\t// Use original msgCount (pre-injection) so actual-based estimation stays consistent across turns\n\t// Use actual-based totalTokens as estimate when available (more accurate than full recount)\n\tfinalEstimate := totalTokens\n\tif s.sawtoothTrigger == nil || s.sawtoothTrigger.GetLastTokens(threadID) == 0 {\n\t\t// First request: no actual yet, do a full recount of post-injection messages\n\t\tfinalMsgs, _ := req[\"messages\"].([]any)\n\t\tfinalEstimate = s.measureOverhead(req) + s.countMessageTokens(finalMsgs)\n\t}\n\ts.logger.Printf(\"[req %d %s tid=%s] FINAL: ~%dk estimated (after all injections)\", reqIdx, proj, threadID, finalEstimate/1000)\n\tif raw := s.getRawEstimate(threadID); raw > 0 {\n\t\ts.cacheStatusWriter.UpdateRawForThread(threadID, raw)\n\t}\n\ts.forwardWithAnnotation(w, r, body, reqIdx, toolUseIDs, proj, threadID, msgCount, finalEstimate)\n}\n\n// getPromptFlags resolves the effective prompt flags for a profile, falling back\n// to the Server's legacy flat config if promptCfg is nil (backward compatibility).\nfunc (s *Server) getPromptFlags(profile models.PromptProfile) *config.PromptFlags {\n\tif s.promptCfg != nil {\n\t\treturn s.promptCfg.EffectivePromptFlags(profile)\n\t}\n\t// Legacy path: use flat fields as Claude defaults.\n\treturn &config.PromptFlags{\n\t\tUngate: s.cfg.PromptUngate,\n\t\tRewrite: s.cfg.PromptRewrite,\n\t\tEnhance: s.cfg.PromptEnhance,\n\t\tToolPrefs: s.cfg.PromptToolPrefs,\n\t\tOutputDiscipline: s.cfg.PromptOutputDiscipline,\n\t\tCodingDiscipline: s.cfg.PromptCodingDiscipline,\n\t\tBeweislast: s.cfg.PromptBeweislast,\n\t\tFable: s.cfg.PromptFable,\n\t\tScopeDiscipline: s.cfg.PromptScopeDiscipline,\n\t\tDelegationContract: s.cfg.PromptDelegationContract,\n\t\tClarifyFirst: s.cfg.PromptClarifyFirst,\n\t\tCodeToolsFirst: s.cfg.PromptCodeToolsFirst,\n\t\tWikiFirst: s.cfg.PromptWikiFirst,\n\t\tPatternSuggest: s.cfg.PromptPatternSuggest,\n\t}\n}\n\n// getThreadCWD returns the cached working directory for a thread.\nfunc (s *Server) getThreadCWD(threadID string) string {\n\ts.threadCWDMu.RLock()\n\tdefer s.threadCWDMu.RUnlock()\n\tif s.threadCWD == nil {\n\t\treturn \"\"\n\t}\n\treturn s.threadCWD[threadID]\n}\n\n// setThreadCWD caches the working directory for a thread.\nfunc (s *Server) setThreadCWD(threadID, cwd string) {\n\ts.threadCWDMu.Lock()\n\tdefer s.threadCWDMu.Unlock()\n\tif s.threadCWD == nil {\n\t\ts.threadCWD = make(map[string]string)\n\t}\n\ts.threadCWD[threadID] = cwd\n}\n\n// resolveOpenAITarget returns the upstream URL for an OpenAI-format request.\n// Checks ProviderTargets by prefix match (e.g. \"deepseek\" matches \"deepseek-v4-pro\"),\n// then by exact model name, then falls back to OpenAITargetURL.\nfunc (s *Server) resolveOpenAITarget(model string) string {\n\tmodelLower := strings.ToLower(model)\n\tif len(s.cfg.ProviderTargets) > 0 {\n\t\tfor key, url := range s.cfg.ProviderTargets {\n\t\t\tif url == \"\" {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tkeyLower := strings.ToLower(key)\n\t\t\tif strings.HasPrefix(modelLower, keyLower) || strings.HasPrefix(modelLower, keyLower+\"-\") {\n\t\t\t\treturn strings.TrimRight(url, \"/\")\n\t\t\t}\n\t\t}\n\t\tfor key, url := range s.cfg.ProviderTargets {\n\t\t\tif url != \"\" && strings.EqualFold(key, model) {\n\t\t\t\treturn strings.TrimRight(url, \"/\")\n\t\t\t}\n\t\t}\n\t}\n\t// Auto-discovered targets (secondary — explicit provider_targets takes priority)\n\tif len(s.autoProviderTargets) > 0 {\n\t\tif url, ok := s.autoProviderTargets[modelLower]; ok && url != \"\" {\n\t\t\treturn url\n\t\t}\n\t}\n\n\tif s.cfg.OpenAITargetURL != \"\" {\n\t\treturn s.cfg.OpenAITargetURL\n\t}\n\treturn s.cfg.TargetURL\n}\n\n// resolveAnthropicTarget returns the upstream URL for an Anthropic-format request.\n// Checks ProviderTargets by prefix match (e.g. \"deepseek\" matches \"deepseek-v4-pro\"),\n// then by exact model name, then falls back to TargetURL.\nfunc (s *Server) resolveAnthropicTarget(model string) string {\n\tmodelLower := strings.ToLower(model)\n\tif len(s.cfg.ProviderTargets) > 0 {\n\t\tfor key, url := range s.cfg.ProviderTargets {\n\t\t\tif url == \"\" {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tkeyLower := strings.ToLower(key)\n\t\t\tif strings.HasPrefix(modelLower, keyLower) || strings.HasPrefix(modelLower, keyLower+\"-\") {\n\t\t\t\treturn strings.TrimRight(url, \"/\")\n\t\t\t}\n\t\t}\n\t\tfor key, url := range s.cfg.ProviderTargets {\n\t\t\tif url != \"\" && strings.EqualFold(key, model) {\n\t\t\t\treturn strings.TrimRight(url, \"/\")\n\t\t\t}\n\t\t}\n\t}\n\treturn s.cfg.TargetURL\n}\n\n// bytesPerToken is the approximation factor for token estimation.\n// Anthropic recommends ~3.5 chars/token; we use 3.6 for slight overestimation\n// (triggers stubbing a bit earlier = safer for context budget).\nconst bytesPerToken = 3.6\n\n// TokenEstimateFunc is the central token estimation function.\n// All stubbing/budget decisions use this. Receives extracted text content\n// (not JSON wire format) so the tokenizer counts actual content tokens.\ntype TokenEstimateFunc func(text string) int\n"}, "7cb5e24b0fb42a138654f4270f60e44382667266": {"t": 1785759341.6174428, "n": 77036, "h": "dceb8f035fa6bc6b", "c": "package proxy\n\nimport (\n\t\"context\"\n\t\"crypto/tls\"\n\t\"encoding/json\"\n\t\"fmt\"\n\t\"io\"\n\t\"log\"\n\t\"net\"\n\t\"net/http\"\n\t\"os\"\n\t\"os/signal\"\n\t\"path/filepath\"\n\t\"strconv\"\n\t\"strings\"\n\t\"sync\"\n\t\"sync/atomic\"\n\t\"syscall\"\n\t\"time\"\n\n\t\"github.com/carsteneu/yesmem/internal/buildinfo\"\n\t\"github.com/carsteneu/yesmem/internal/config\"\n\t\"github.com/carsteneu/yesmem/internal/models\"\n\ttokenizer \"github.com/qhenkart/anthropic-tokenizer-go\"\n)\n\n// Config holds proxy configuration.\ntype Config struct {\n\tListenAddr string // e.g. \":9099\"\n\tTargetURL string // e.g. \"https://api.anthropic.com\"\n\tTokenThreshold int // trigger stubbing above this estimated token count\n\tTokenMinimumThreshold int // stub down to this floor\n\tTokenThresholds map[string]int // model-specific thresholds: {\"opus\": 180000, \"haiku\": 130000}\n\tKeepRecent int // number of messages to always keep unmodified\n\tDataDir string // yesmem data directory for DB access\n\tOpenAITargetURL string // upstream for OpenAI-format clients; if empty, uses TargetURL\n\tProviderTargets map[string]string // per-provider upstream URLs, e.g. {\"deepseek\": \"https://api.deepseek.com\"}\n\tAutoConfigureProviders bool // enable auto-discovery of provider targets from opencode config\n\n\t// Signal reflection\n\tSignalsEnabled bool // enable async signal reflection calls\n\tSignalsMode string // \"reflection\" = separate API call\n\tSignalsEveryNTurns int // every N end_turn responses\n\tSignalsModel string // full model ID, e.g. \"claude-sonnet-4-6\"\n\tAPIKey string // needed for reflection API calls\n\n\t// Sawtooth cache optimization\n\tSawtoothEnabled bool // use sawtooth instead of progressive decay (default: true)\n\tCacheTTL string // \"ephemeral\" (5m) or \"1h\" (extended)\n\n\t// Usage deflation: scale down input_tokens reported to CC to suppress \"Context low\" warning.\n\t// 0 = disabled, 0.7 = report 70% of actual tokens. Real values kept for internal tracking.\n\tUsageDeflationFactor float64\n\n\t// System prompt rewriting\n\tPromptUngate bool // strip \"may or may not be relevant\" disclaimer from CLAUDE.md injection\n\tPromptRewrite bool // strip \"Output efficiency\" + \"short and concise\", inject Ant-quality directives\n\tPromptEnhance bool // CLAUDE.md authority reinforcement, comment discipline, persona-based tone\n\tPromptToolPrefs bool // inject [yesmem-tool-prefs] Edit/Write preference + error-semantics warning\n\tPromptOutputDiscipline bool // inject [yesmem-output-discipline] no-preamble + no-skill-eval + exploratory-heuristic\n\tPromptCodingDiscipline bool // inject [yesmem-coding-discipline] read-before-propose + no-brute-force + no-half-finished\n\tPromptBeweislast bool // inject [yesmem-beweislast] fabrication-guard + claim-vs-proof + stance-under-challenge + tool-result-honesty + long-context-erosion\n\tPromptFable bool // inject [yesmem-fable] baseline-comparison + rollback-path + readback-check for high-stakes changes\n\tPromptScopeDiscipline bool // inject [yesmem-scope-discipline] deliver-A-not-A+B+C + adjacent-findings-separate + scope-bound-authorization\n\tPromptDelegationContract bool // inject [yesmem-delegation-contract] self-contained-prompts + parallel-dispatch\n\tPromptClarifyFirst bool // inject [yesmem-clarify-first] clarify only when alternative interpretations produce materially different work\n\tPromptCodeToolsFirst bool // inject [yesmem-code-tools-first] prefer MCP code-navigation tools over Agent spawns\n\tPromptWikiFirst bool // inject [yesmem-wiki-first] check per-file wiki BEFORE editing\n\tPromptPatternSuggest bool // record repeated shell-command shapes for offline cap-suggestion analysis\n\tEffortFloor string // minimum effort level: \"low\", \"medium\", \"high\", \"max\" (empty = off)\n\tSkillEvalInject string // \"true\" = verbose eval, \"silent\" = internal eval only, \"false\" = disabled\n\n\t// Cache keepalive\n\tCacheKeepaliveEnabled bool\n\tCacheKeepaliveMode string // \"auto\", \"5m\", \"1h\"\n\tCacheKeepalivePings5m int\n\tCacheKeepalivePings1h int\n\tCacheKeepaliveMinMessages int // skip keepalive when request body has fewer messages (0 = always)\n\n\t// Forked agents\n\tForkedAgentsEnabled bool\n\tForkedAgentsModel string // full model ID\n\tForkedAgentsTokenGrowthTrigger int\n\tForkedAgentsMaxFailures int\n\tForkedAgentsMaxForksPerSession int\n\tForkedAgentsDebug bool\n\n\t// Quality model fallback for forked agents\n\tQualityModelID string\n\n\t// Cache state management\n\tResetCache bool // clear persisted frozen stubs and decay state on startup\n\n\t// ModelFeatures: per-model behavioral feature gates (prefix-matched).\n\t// Falls back to FeatureDefaults for models not listed.\n\tModelFeatures map[string]*config.FeatureGates `yaml:\"model_features\"`\n\tFeatureDefaults *config.FeatureGates `yaml:\"feature_defaults\"`\n\n\t// Custom system prompt: shared SYSTEM.md template for OpenCode and Claude Code pipelines.\n\tCustomSystemPrompt CustomSystemPromptConfig\n}\n\n// CustomSystemPromptConfig controls which proxy pipelines use the SYSTEM.md template.\ntype CustomSystemPromptConfig struct {\n\tEnabledOpenCode bool\n\tEnabledClaudeCode bool\n\tEnabledCodex bool\n\tTemplatePath string\n\tModelTemplates map[string]string // per-model overrides: modelPattern → templatePath\n}\n\nconst maxAnnotations = 5000 // evict oldest when exceeded\n\n// ANSI color codes for log output\nconst (\n\tcolorReset = \"\\033[0m\"\n\tcolorDim = \"\\033[2m\" // gray — passthrough / no stubbing\n\tcolorGreen = \"\\033[32m\" // stubbed / compressed\n\tcolorLightGreen = \"\\033[92m\" // FINAL without compression\n\tcolorBlue = \"\\033[34m\" // injection\n\tcolorOrange = \"\\033[33m\" // skip / retry\n\tcolorYellow = \"\\033[33m\" // warnings\n\tcolorRed = \"\\033[31m\" // errors\n)\n\n// Server is the infinite-thread proxy.\ntype Server struct {\n\tcfg Config\n\tautoProviderTargets map[string]string // auto-discovered modelID → upstreamURL\n\thttpClient *http.Client\n\tlogger *log.Logger\n\tversion string // build version for log tracing\n\trequestIdx atomic.Int64\n\n\t// Post-hoc annotations: tool_use_id → first ~120 chars of Claude's response\n\tmu sync.RWMutex\n\tannotations map[string]string\n\n\t// Progressive decay tracker\n\tdecay *DecayTracker\n\n\t// Living narrative block\n\tnarrative *Narrative\n\n\t// Pivot moments cache (from daemon)\n\tpivotMu sync.RWMutex\n\tpivotTexts []string\n\tpivotCached time.Time\n\n\t// Aggregate stats (Task #2)\n\tstats *ProxyStats\n\n\t// Local tokenizer for accurate token counting\n\ttokenizer *tokenizer.Tokenizer\n\n\t// Cached overhead tokens (system + tools), set on first request per thread\n\toverheadMu sync.RWMutex\n\toverheadTokens int // unused, kept for interface compat\n\n\t// Hysteresis state for stubbing (Task #6)\n\tstubActive atomic.Bool\n\n\t// Daemon connection state — first successful connect flips to true\n\tdaemonReady atomic.Bool\n\n\t// Retry detection (Task #7)\n\tretryMu sync.RWMutex\n\tlastFingerprint string\n\n\t// Session start time for temporal annotation in archive blocks\n\tsessionStartMu sync.RWMutex\n\tsessionStartTime time.Time\n\n\t// Runtime config override (set via MCP set_config tool)\n\tconfigOverrideMu sync.RWMutex\n\ttokenThresholdOverrides map[string]int // model-key → threshold, \"\" = global fallback\n\n\t// Prompt rewrite miss logging: function+Claude Code version → last log time\n\trewriteMissMu sync.Mutex\n\trewriteMissLog map[string]time.Time\n\n\t// Briefing cache — keyed by threadID. Each Claude Code session thread gets\n\t// its own briefing+codemap snapshot so a sawtooth refreeze on thread A\n\t// cannot invalidate thread B's cached message-prefix hash. The cache entry\n\t// remembers which project it was loaded for; switching project for the\n\t// same thread evicts the old entry on next read.\n\tbriefingMu sync.RWMutex\n\tbriefingCache map[string]briefingEntry\n\n\t// briefingLoader is an optional test-only seam for refreshBriefing.\n\t// Nil in production → refreshBriefing falls back to s.loadBriefing.\n\tbriefingLoader func(project, projectDir string) briefingData\n\n\t// Cognitive signal bus — routes _signal_* tool calls to handlers\n\tsignalBus *SignalBus\n\n\t// Inline reflection: track which learning IDs were injected per thread\n\tlastInjectedIDsMu sync.Mutex\n\tlastInjectedIDs map[string]map[int64]string // threadID → id → source (\"briefing\"|\"associative\"|\"fresh\")\n\tsessionInjectCounts map[string]map[int64]int // threadID → learningID → injection count this session\n\tlastTurnInjected map[string]map[int64]bool // threadID → learningIDs injected in previous turn\n\n\t// Self-priming anchors: threadID → last self-prime text\n\tselfPrimeMu sync.RWMutex\n\tselfPrimes map[string]string\n\n\t// Timestamp tracking: threadID → time of last response completion\n\tresponseTsMu sync.RWMutex\n\tresponseTimes map[string]time.Time\n\n\t// Think reminder: per-thread request counter (replaces hook-think file-based counter)\n\tthinkMu sync.Mutex\n\tthinkCounters map[string]int // threadID → request count\n\n\tchannelMu sync.Mutex\n\tchannelInjectCount map[string]int // sessionID → injection turn count\n\n\t// Prompt cache gating — enables cache_control breakpoints when requests are frequent\n\tcacheGate *CacheGate\n\n\t// Sawtooth cache optimization\n\tfrozenStubs *FrozenStubs\n\teagerStubMemory *EagerStubMemory\n\tcapsCache *CapsCache\n\tsawtoothTrigger *SawtoothTrigger\n\ttimestampStore *TimestampStore\n\tcacheStatusWriter *CacheStatusWriter\n\tcacheKeepalive *CacheKeepalive\n\tcacheTTLDetector *CacheTTLDetector\n\t// promptCfg is the profile-aware prompt configuration from config.ProxyConfig.\n\t// Used to resolve EffectivePromptFlags(profile) for multi-agent prompt isolation.\n\tpromptCfg *config.ProxyConfig\n\t// threadCWD caches the working directory per thread (for opencode: only in first message)\n\tthreadCWDMu sync.RWMutex\n\tthreadCWD map[string]string // threadID → cwd\n\n\t// Cumulative token savings from collapsing (per threadID)\n\trawSavingsMu sync.Mutex\n\trawSavings map[string]int\n\n\t// Skill hint tracking: per-thread active skills\n\tskillTracker *skillHintTracker\n\n\t// Forked agent state: per-thread token growth + failure tracking\n\tforkState *ForkState\n\tforkConfigs []ForkConfig\n\n\t// Custom system prompt template for OpenCode (loaded from DataDir/SYSTEM.md)\n\tcustomSystemPrompt []byte\n\n\t// Per-model template overrides (loaded from config CustomSystemPrompt.ModelTemplates)\n\tmodelTemplates map[string][]byte\n\n\t// Rules re-injection: condensed CLAUDE.md rules injected every ~40k tokens\n\trulesMu sync.RWMutex\n\trulesBlock string // cached condensed rules (fetched once from daemon)\n\trulesTokenCount map[string]int // threadID → tokens since last rules injection\n\trulesCollapseInjected map[string]bool // threadID → true if collapse already injected rules (reset by normal inject)\n\tmsgCounters *msgCounters // global per-thread msg counter, persists across collapses\n\n\t// Loop detection: per-thread warning state\n\tloopMu sync.Mutex\n\tloopStates map[string]*LoopState // threadID → state\n\n\t// Injection overhead: per-thread delta between API-actual and local BPE estimate\n\tinjectionOverheadMu sync.RWMutex\n\tinjectionOverhead map[string]int // threadID → overhead tokens\n}\n\n// Run starts the proxy server and blocks until interrupted.\nfunc Run(cfg Config) error {\n\ts := &Server{\n\t\tcfg: cfg,\n\t\thttpClient: &http.Client{\n\t\t\tTimeout: 10 * time.Minute,\n\t\t\tTransport: &http.Transport{\n\t\t\t\tTLSClientConfig: &tls.Config{},\n\t\t\t\tMaxIdleConns: 10,\n\t\t\t\tIdleConnTimeout: 90 * time.Second,\n\t\t\t\tDisableCompression: true,\n\t\t\t\tResponseHeaderTimeout: 60 * time.Second,\n\t\t\t},\n\t\t},\n\t\tlogger: createLogger(cfg.DataDir),\n\t\tannotations: make(map[string]string),\n\t\tdecay: NewDecayTracker(),\n\t\tnarrative: NewNarrative(),\n\t\tstats: &ProxyStats{startTime: time.Now()},\n\t\tselfPrimes: make(map[string]string),\n\t\tlastInjectedIDs: make(map[string]map[int64]string),\n\t\tversion: buildinfo.Version,\n\t\tsessionInjectCounts: make(map[string]map[int64]int),\n\t\tlastTurnInjected: make(map[string]map[int64]bool),\n\t\tresponseTimes: make(map[string]time.Time),\n\t\tthinkCounters: make(map[string]int),\n\t\tchannelInjectCount: make(map[string]int),\n\t\trewriteMissLog: make(map[string]time.Time),\n\t\tcacheGate: NewCacheGate(cacheGapForTTL(cfg.CacheTTL)),\n\t\tfrozenStubs: NewFrozenStubsWithTTL(sawtoothTTLForCacheTTL(cfg.CacheTTL)),\n\t\teagerStubMemory: NewEagerStubMemory(),\n\t\tcapsCache: NewCapsCache(),\n\t\ttimestampStore: NewTimestampStore(),\n\t\tsawtoothTrigger: NewSawtoothTrigger(cacheGapForTTL(cfg.CacheTTL), cfg.TokenThreshold, cfg.TokenMinimumThreshold),\n\t\tcacheStatusWriter: NewCacheStatusWriter(cfg.DataDir, cfg.CacheTTL, cfg.TokenThreshold, cfg.TokenMinimumThreshold),\n\t\tcacheTTLDetector: NewCacheTTLDetectorWithPersist(cfg.DataDir),\n\t\tskillTracker: newSkillHintTracker(),\n\t\trulesTokenCount: make(map[string]int),\n\t\trulesCollapseInjected: make(map[string]bool),\n\t\tmsgCounters: newMsgCounters(),\n\t\tloopStates: make(map[string]*LoopState),\n\t\tinjectionOverhead: make(map[string]int),\n\t\tforkState: NewForkState(cfg.ForkedAgentsTokenGrowthTrigger, 60000, cfg.ForkedAgentsMaxFailures, cfg.ForkedAgentsMaxForksPerSession),\n\t\tforkConfigs: []ForkConfig{},\n\t}\n\n\t// Load custom system prompt from configured path\n\tif cfg.CustomSystemPrompt.TemplatePath != \"\" &&\n\t\t(cfg.CustomSystemPrompt.EnabledOpenCode || cfg.CustomSystemPrompt.EnabledClaudeCode || cfg.CustomSystemPrompt.EnabledCodex) {\n\t\ts.customSystemPrompt = loadSystemPromptFromPath(cfg.CustomSystemPrompt.TemplatePath)\n\t\tif s.customSystemPrompt != nil {\n\t\t\ts.logger.Printf(\"[proxy] loaded custom system prompt from %s (%d bytes, opencode=%v cc=%v codex=%v)\",\n\t\t\t\tcfg.CustomSystemPrompt.TemplatePath, len(s.customSystemPrompt),\n\t\t\t\tcfg.CustomSystemPrompt.EnabledOpenCode, cfg.CustomSystemPrompt.EnabledClaudeCode, cfg.CustomSystemPrompt.EnabledCodex)\n\t\t} else {\n\t\t\ts.logger.Printf(\"[proxy] WARNING: custom_system_prompt enabled but template not found at %q — feature disabled\",\n\t\t\t\tcfg.CustomSystemPrompt.TemplatePath)\n\t\t}\n\t}\n\n\t// Load per-model template overrides\n\tif len(cfg.CustomSystemPrompt.ModelTemplates) > 0 {\n\t\ts.modelTemplates = make(map[string][]byte, len(cfg.CustomSystemPrompt.ModelTemplates))\n\t\tfor pattern, path := range cfg.CustomSystemPrompt.ModelTemplates {\n\t\t\ttpl := loadSystemPromptFromPath(path)\n\t\t\tif tpl != nil {\n\t\t\t\ts.modelTemplates[pattern] = tpl\n\t\t\t\ts.logger.Printf(\"[proxy] loaded model template %q from %s (%d bytes)\", pattern, path, len(tpl))\n\t\t\t} else {\n\t\t\t\ts.logger.Printf(\"[proxy] WARNING: model template %q not found at %q — skipping\", pattern, path)\n\t\t\t}\n\t\t}\n\t}\n\n\t// Log persisted detection state\n\tif sup := s.cacheTTLDetector.Is1hSupported(); sup != nil {\n\t\ts.logger.Printf(\"[proxy] loaded persisted TTL detection: 1h_supported=%v\", *sup)\n\t\tif *sup && s.frozenStubs != nil {\n\t\t\ts.frozenStubs.UpdateTTL(sawtoothTTLForCacheTTL(\"1h\"))\n\t\t}\n\t}\n\n\tif cfg.CacheKeepaliveEnabled {\n\t\ts.cacheKeepalive = NewCacheKeepalive(CacheKeepaliveConfig{\n\t\t\tTarget: cfg.TargetURL,\n\t\t\tProviderTargets: cfg.ProviderTargets,\n\t\t\tMode: cfg.CacheKeepaliveMode,\n\t\t\tPings5m: cfg.CacheKeepalivePings5m,\n\t\t\tPings1h: cfg.CacheKeepalivePings1h,\n\t\t\tMinMessages: cfg.CacheKeepaliveMinMessages,\n\t\t\tDetector: s.cacheTTLDetector,\n\t\t\tLogger: s.logger,\n\t\t\tOnPing: func(threadID string, cacheRead, cacheWrite int) {\n\t\t\t\ts.cacheTTLDetector.RecordResponse(cacheRead, cacheWrite, 0)\n\t\t\t\tif s.frozenStubs != nil {\n\t\t\t\t\ts.frozenStubs.Touch(threadID)\n\t\t\t\t}\n\t\t\t\tif s.sawtoothTrigger != nil {\n\t\t\t\t\ts.sawtoothTrigger.TouchRequestTime(threadID)\n\t\t\t\t}\n\t\t\t},\n\t\t})\n\t}\n\n\t// Wire detector + keepalive to status writer for terminal display\n\ts.cacheStatusWriter.SetDetector(s.cacheTTLDetector)\n\tif s.cacheKeepalive != nil {\n\t\ts.cacheStatusWriter.SetKeepalive(s.cacheKeepalive)\n\t}\n\n\t// Register forked agent configs\n\tif cfg.ForkedAgentsEnabled {\n\t\tmodel := cfg.ForkedAgentsModel\n\t\ts.forkConfigs = append(s.forkConfigs, NewExtractAndEvaluateConfig(model))\n\t\tmodelDesc := model\n\t\tif modelDesc == \"\" {\n\t\t\tmodelDesc = \"(same as main thread)\"\n\t\t}\n\t\ts.logger.Printf(\"[proxy] forked agents enabled: %d configs, model=%s, trigger=%dk tokens\",\n\t\t\tlen(s.forkConfigs), modelDesc, cfg.ForkedAgentsTokenGrowthTrigger/1000)\n\t}\n\n\t// Wire sawtooth token persistence via daemon RPC\n\ts.sawtoothTrigger.SetPersistFunc(func(key, value string) {\n\t\tif _, err := s.queryDaemon(\"set_proxy_state\", map[string]any{\"key\": key, \"value\": value}); err != nil {\n\t\t\ts.logger.Printf(\"[sawtooth] persist failed for %s: %v\", key, err)\n\t\t}\n\t})\n\ts.sawtoothTrigger.SetLoadFunc(func(key string) (string, bool) {\n\t\tresult, err := s.queryDaemon(\"get_proxy_state\", map[string]any{\"key\": key})\n\t\tif err != nil || result == nil {\n\t\t\treturn \"\", false\n\t\t}\n\t\tvar resp struct {\n\t\t\tValue string `json:\"value\"`\n\t\t}\n\t\tif json.Unmarshal(result, &resp) != nil || resp.Value == \"\" {\n\t\t\treturn \"\", false\n\t\t}\n\t\treturn resp.Value, true\n\t})\n\n\t// Wire frozen stubs persistence via same daemon RPC\n\ts.frozenStubs.SetPersistFunc(func(key, value string) {\n\t\tif _, err := s.queryDaemon(\"set_proxy_state\", map[string]any{\"key\": key, \"value\": value}); err != nil {\n\t\t\ts.logger.Printf(\"[frozen] persist failed for %s: %v\", key, err)\n\t\t}\n\t})\n\ts.frozenStubs.SetLoadFunc(func(key string) (string, bool) {\n\t\tresult, err := s.queryDaemon(\"get_proxy_state\", map[string]any{\"key\": key})\n\t\tif err != nil || result == nil {\n\t\t\treturn \"\", false\n\t\t}\n\t\tvar resp struct {\n\t\t\tValue string `json:\"value\"`\n\t\t}\n\t\tif json.Unmarshal(result, &resp) != nil || resp.Value == \"\" {\n\t\t\treturn \"\", false\n\t\t}\n\t\treturn resp.Value, true\n\t})\n\n\t// Wire eager-stub memory persistence via same daemon RPC.\n\t// Persists per-thread tool_use_id sets so stub decisions survive deploys.\n\ts.eagerStubMemory.SetPersistFunc(func(key, value string) {\n\t\tif _, err := s.queryDaemon(\"set_proxy_state\", map[string]any{\"key\": key, \"value\": value}); err != nil {\n\t\t\ts.logger.Printf(\"[eagerstub] persist failed for %s: %v\", key, err)\n\t\t}\n\t})\n\ts.eagerStubMemory.SetLoadFunc(func(key string) (string, bool) {\n\t\tresult, err := s.queryDaemon(\"get_proxy_state\", map[string]any{\"key\": key})\n\t\tif err != nil || result == nil {\n\t\t\treturn \"\", false\n\t\t}\n\t\tvar resp struct {\n\t\t\tValue string `json:\"value\"`\n\t\t}\n\t\tif json.Unmarshal(result, &resp) != nil || resp.Value == \"\" {\n\t\t\treturn \"\", false\n\t\t}\n\t\treturn resp.Value, true\n\t})\n\n\t// Wire decay tracker persistence via same daemon RPC\n\ts.decay.SetPersistFunc(func(key, value string) {\n\t\tif _, err := s.queryDaemon(\"set_proxy_state\", map[string]any{\"key\": key, \"value\": value}); err != nil {\n\t\t\ts.logger.Printf(\"[decay] persist failed for %s: %v\", key, err)\n\t\t}\n\t})\n\ts.decay.SetLoadFunc(func(key string) (string, bool) {\n\t\tresult, err := s.queryDaemon(\"get_proxy_state\", map[string]any{\"key\": key})\n\t\tif err != nil || result == nil {\n\t\t\treturn \"\", false\n\t\t}\n\t\tvar resp struct {\n\t\t\tValue string `json:\"value\"`\n\t\t}\n\t\tif json.Unmarshal(result, &resp) != nil || resp.Value == \"\" {\n\t\t\treturn \"\", false\n\t\t}\n\t\treturn resp.Value, true\n\t})\n\n\t// Wire timestamp store persistence via same daemon RPC\n\ts.timestampStore.SetPersistFunc(func(key, value string) {\n\t\tif _, err := s.queryDaemon(\"set_proxy_state\", map[string]any{\"key\": key, \"value\": value}); err != nil {\n\t\t\ts.logger.Printf(\"[timestamps] persist failed for %s: %v\", key, err)\n\t\t}\n\t})\n\ts.timestampStore.SetLoadFunc(func(key string) (string, bool) {\n\t\tresult, err := s.queryDaemon(\"get_proxy_state\", map[string]any{\"key\": key})\n\t\tif err != nil || result == nil {\n\t\t\treturn \"\", false\n\t\t}\n\t\tvar resp struct {\n\t\t\tValue string `json:\"value\"`\n\t\t}\n\t\tif json.Unmarshal(result, &resp) != nil || resp.Value == \"\" {\n\t\t\treturn \"\", false\n\t\t}\n\t\treturn resp.Value, true\n\t})\n\n\t// Clear persisted cache state if --reset-cache was passed\n\tif cfg.ResetCache {\n\t\tfor _, prefix := range []string{\"frozen:\", \"decay:\", \"timestamps:\"} {\n\t\t\tresult, err := s.queryDaemon(\"delete_proxy_state_prefix\", map[string]any{\"prefix\": prefix})\n\t\t\tif err != nil {\n\t\t\t\ts.logger.Printf(\"[reset-cache] failed to delete %s*: %v\", prefix, err)\n\t\t\t} else {\n\t\t\t\tvar resp struct{ Deleted int }\n\t\t\t\tjson.Unmarshal(result, &resp)\n\t\t\t\ts.logger.Printf(\"[reset-cache] deleted %d %s* entries\", resp.Deleted, prefix)\n\t\t\t}\n\t\t}\n\t}\n\n\t// Init signal bus with daemon RPC as callback\n\ts.signalBus = NewSignalBus(s.logger)\n\tregisterSignalHandlers(s.signalBus, s.logger, s.queryDaemon, func(threadID, anchor string) {\n\t\ts.selfPrimeMu.Lock()\n\t\ts.selfPrimes[threadID] = anchor\n\t\ts.selfPrimeMu.Unlock()\n\t})\n\n\t// Init local tokenizer (BPE-based, ~60ms)\n\ttok, err := tokenizer.New()\n\tif err != nil {\n\t\ts.logger.Printf(\"%s[proxy] tokenizer init failed, falling back to heuristic: %v%s\", colorOrange, err, colorReset)\n\t} else {\n\t\ts.tokenizer = tok\n\t\ts.logger.Printf(\"[proxy] tokenizer initialized\")\n\t}\n\n\t// Auto-discover provider targets from opencode config\n\tif cfg.AutoConfigureProviders {\n\t\ts.autoProviderTargets = runAutoDiscovery(s.logger, cfg.ProviderTargets)\n\t} else {\n\t\ts.logger.Printf(\"[proxy] auto_configure_providers is disabled — skipping auto-discovery\")\n\t}\n\n\tmux := http.NewServeMux()\n\tmux.HandleFunc(\"/health\", s.handleHealth)\n\tmux.HandleFunc(\"/\", s.handleRequest)\n\n\tsrv := &http.Server{\n\t\tAddr: sanitizeListenAddr(cfg.ListenAddr),\n\t\tHandler: mux,\n\t}\n\n\tdone := make(chan os.Signal, 1)\n\tsignal.Notify(done, syscall.SIGINT, syscall.SIGTERM)\n\n\tgo func() {\n\t\t<-done\n\t\ts.logger.Println(\"shutting down (draining in-flight requests)...\")\n\t\tif s.cacheKeepalive != nil {\n\t\t\ts.cacheKeepalive.Stop()\n\t\t}\n\t\tctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)\n\t\tdefer cancel()\n\t\tif err := srv.Shutdown(ctx); err != nil {\n\t\t\ts.logger.Printf(\"graceful shutdown failed: %v, forcing close\", err)\n\t\t\tsrv.Close()\n\t\t}\n\t}()\n\n\ts.logger.Printf(\"listening on %s → %s (threshold=%d, keepRecent=%d, model_thresholds=%v)\",\n\t\tsanitizeListenAddr(cfg.ListenAddr), cfg.TargetURL, cfg.TokenThreshold, cfg.KeepRecent, cfg.TokenThresholds)\n\n\tif err := srv.ListenAndServe(); err != http.ErrServerClosed {\n\t\treturn fmt.Errorf(\"listen: %w\", err)\n\t}\n\treturn nil\n}\n\n// effectiveTokenThreshold returns the runtime token threshold for a given model.\n// Priority: session override (via set_config with session_id) > model-specific override (MCP set_config token_threshold:)\n// > model-specific override (config TokenThresholds) > global override (set_config token_threshold) > config global default.\nfunc (s *Server) effectiveTokenThreshold(model string) int {\n\ts.configOverrideMu.RLock()\n\toverrides := s.tokenThresholdOverrides\n\ts.configOverrideMu.RUnlock()\n\t// 1. MCP override (model-specific)\n\tif len(overrides) > 0 && model != \"\" {\n\t\tlower := strings.ToLower(model)\n\t\tfor key, threshold := range overrides {\n\t\t\tif key != \"\" && strings.Contains(lower, strings.ToLower(key)) && threshold > 0 {\n\t\t\t\treturn threshold\n\t\t\t}\n\t\t}\n\t}\n\t// 2. Config model-specific\n\tif model != \"\" && len(s.cfg.TokenThresholds) > 0 {\n\t\tlower := strings.ToLower(model)\n\t\tfor key, threshold := range s.cfg.TokenThresholds {\n\t\t\tif strings.Contains(lower, strings.ToLower(key)) {\n\t\t\t\treturn threshold\n\t\t\t}\n\t\t}\n\t}\n\t// 3. Global override fallback (key \"\")\n\tif len(overrides) > 0 {\n\t\tif globalOverride, ok := overrides[\"\"]; ok && globalOverride > 0 {\n\t\t\treturn globalOverride\n\t\t}\n\t}\n\t// 4. Config global default\n\treturn s.cfg.TokenThreshold\n}\n\n// SetTokenThresholdOverride sets a runtime override for the token threshold per model key.\n// Pass threshold=0 to reset the override for that key. Key \"\" is the global fallback.\nfunc (s *Server) SetTokenThresholdOverride(modelKey string, threshold int) {\n\ts.configOverrideMu.Lock()\n\tif s.tokenThresholdOverrides == nil {\n\t\ts.tokenThresholdOverrides = make(map[string]int)\n\t}\n\tif threshold <= 0 {\n\t\tdelete(s.tokenThresholdOverrides, modelKey)\n\t} else {\n\t\ts.tokenThresholdOverrides[modelKey] = threshold\n\t}\n\ts.configOverrideMu.Unlock()\n\n\t// Propagate global threshold to sawtooth trigger so runtime overrides take effect\n\tif modelKey == \"\" && s.sawtoothTrigger != nil {\n\t\tif threshold > 0 {\n\t\t\ts.sawtoothTrigger.SetTokenThreshold(threshold)\n\t\t} else {\n\t\t\ts.sawtoothTrigger.SetTokenThreshold(s.cfg.TokenThreshold)\n\t\t}\n\t}\n}\n\n// refreshConfigOverrides loads runtime config overrides from daemon proxy_state.\n// Checks in priority order: session-specific, model-specific, then global.\nfunc (s *Server) refreshConfigOverrides(threadID string) {\n\t// 1. Session-specific override (highest priority)\n\tif threadID != \"\" {\n\t\tresult, err := s.queryDaemon(\"get_proxy_state\", map[string]any{\"key\": \"config_override:token_threshold:\" + threadID})\n\t\tif err == nil && result != nil {\n\t\t\tvar resp struct {\n\t\t\t\tValue string `json:\"value\"`\n\t\t\t}\n\t\t\tif json.Unmarshal(result, &resp) == nil && resp.Value != \"\" {\n\t\t\t\tif v, err := strconv.Atoi(resp.Value); err == nil && v > 0 {\n\t\t\t\t\ts.SetTokenThresholdOverride(\"\", v)\n\t\t\t\t\treturn\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\t// 2. Model-specific overrides (e.g. config_override:token_threshold:deepseek)\n\tfor modelKey := range s.cfg.TokenThresholds {\n\t\tresult, err := s.queryDaemon(\"get_proxy_state\", map[string]any{\"key\": \"config_override:token_threshold:\" + modelKey})\n\t\tif err == nil && result != nil {\n\t\t\tvar resp struct {\n\t\t\t\tValue string `json:\"value\"`\n\t\t\t}\n\t\t\tif json.Unmarshal(result, &resp) == nil && resp.Value != \"\" {\n\t\t\t\tif v, err := strconv.Atoi(resp.Value); err == nil && v > 0 {\n\t\t\t\t\ts.SetTokenThresholdOverride(modelKey, v)\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\t// 3. Global override (fallback)\n\tresult, err := s.queryDaemon(\"get_proxy_state\", map[string]any{\"key\": \"config_override:token_threshold\"})\n\tif err != nil || result == nil {\n\t\treturn\n\t}\n\tvar resp struct {\n\t\tValue string `json:\"value\"`\n\t}\n\tif json.Unmarshal(result, &resp) == nil && resp.Value != \"\" {\n\t\tif v, err := strconv.Atoi(resp.Value); err == nil && v > 0 {\n\t\t\ts.SetTokenThresholdOverride(\"\", v)\n\t\t}\n\t}\n}\n\n// queryDaemon opens a fresh unix socket connection per call (thread-safe, no import cycle).\n// ensureFrozenPersisted checks if frozen stubs for a thread are in the DB.\n// If missing (e.g. daemon was unreachable after deploy), re-persists them.\n// Runs asynchronously to avoid blocking the request path.\nfunc (s *Server) ensureFrozenPersisted(threadID string) {\n\tgo func() {\n\t\tkey := \"frozen:\" + threadID\n\t\tresult, err := s.queryDaemon(\"get_proxy_state\", map[string]any{\"key\": key})\n\t\tif err != nil {\n\t\t\treturn // daemon still unreachable, will retry next request\n\t\t}\n\t\tvar resp struct{ Value string }\n\t\tif json.Unmarshal(result, &resp) == nil && resp.Value != \"\" {\n\t\t\treturn // already persisted\n\t\t}\n\t\t// Not in DB — trigger re-persist from in-memory state\n\t\ts.frozenStubs.Persist(threadID)\n\t\ts.decay.Persist(threadID)\n\t\ts.logger.Printf(\"[frozen] re-persisted stubs for %s (was missing from DB)\", threadID)\n\t}()\n}\n\nfunc (s *Server) queryDaemon(method string, params map[string]any) (json.RawMessage, error) {\n\tif s.cfg.DataDir == \"\" {\n\t\treturn nil, fmt.Errorf(\"no data dir configured\")\n\t}\n\tsockPath := filepath.Join(s.cfg.DataDir, \"daemon.sock\")\n\n\t// On cold start (daemon not yet confirmed ready), retry longer to survive daemon restart\n\tmaxAttempts := 3\n\tif !s.daemonReady.Load() {\n\t\tmaxAttempts = 30 // 30 x 500ms = 15s max wait for daemon startup\n\t}\n\n\tvar conn net.Conn\n\tvar err error\n\tfor attempt := 0; attempt < maxAttempts; attempt++ {\n\t\tconn, err = net.DialTimeout(\"unix\", sockPath, 3*time.Second)\n\t\tif err == nil {\n\t\t\ts.daemonReady.Store(true)\n\t\t\tbreak\n\t\t}\n\t\tif attempt < maxAttempts-1 {\n\t\t\ttime.Sleep(500 * time.Millisecond)\n\t\t}\n\t}\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"dial daemon: %w\", err)\n\t}\n\tdefer conn.Close()\n\t// Briefing generation can take 10-20s (LLM refinement + scanner);\n\t// other RPCs (search, learnings) are fast and fine with 5s.\n\tdeadline := 5 * time.Second\n\tif method == \"generate_briefing\" {\n\t\tdeadline = 30 * time.Second\n\t}\n\tconn.SetDeadline(time.Now().Add(deadline))\n\n\t// JSON-RPC style request/response (matches daemon.SocketServer protocol)\n\ttype request struct {\n\t\tMethod string `json:\"method\"`\n\t\tParams map[string]any `json:\"params,omitempty\"`\n\t}\n\ttype response struct {\n\t\tResult json.RawMessage `json:\"result,omitempty\"`\n\t\tError string `json:\"error,omitempty\"`\n\t}\n\n\tif err := json.NewEncoder(conn).Encode(request{Method: method, Params: params}); err != nil {\n\t\treturn nil, fmt.Errorf(\"send: %w\", err)\n\t}\n\n\tvar resp response\n\tif err := json.NewDecoder(conn).Decode(&resp); err != nil {\n\t\treturn nil, fmt.Errorf(\"recv: %w\", err)\n\t}\n\tif resp.Error != \"\" {\n\t\treturn nil, fmt.Errorf(\"daemon: %s\", resp.Error)\n\t}\n\treturn resp.Result, nil\n}\n\nfunc (s *Server) handleRequest(w http.ResponseWriter, r *http.Request) {\n\tif isResponsesPath(r) {\n\t\ts.handleResponses(w, r)\n\t\treturn\n\t}\n\tif isOpenAIPath(r) {\n\t\ts.handleOpenAICompletions(w, r)\n\t\treturn\n\t}\n\tif r.Method == \"POST\" && strings.HasPrefix(r.URL.Path, \"/v1/messages\") &&\n\t\t!strings.Contains(r.URL.Path, \"/batches\") &&\n\t\t!strings.Contains(r.URL.Path, \"/count_tokens\") {\n\t\ts.handleMessages(w, r)\n\t\treturn\n\t}\n\ts.passthrough(w, r)\n}\n\nfunc (s *Server) handleMessages(w http.ResponseWriter, r *http.Request) {\n\treqIdx := int(s.requestIdx.Add(1))\n\n\t// Track session start time (first request sets it)\n\ts.sessionStartMu.Lock()\n\tif s.sessionStartTime.IsZero() {\n\t\ts.sessionStartTime = time.Now()\n\t}\n\ts.sessionStartMu.Unlock()\n\n\t// Sawtooth: periodic TTL eviction of frozen stubs\n\tif s.cfg.SawtoothEnabled && reqIdx%50 == 0 {\n\t\tif n := s.frozenStubs.Evict(); n > 0 {\n\t\t\ts.logger.Printf(\"[req %d] SAWTOOTH: evicted %d stale frozen stubs\", reqIdx, n)\n\t\t}\n\t}\n\n\t// Task #9: Bypass — skip all proxy logic\n\tif isBypassed(r.Header) {\n\t\ts.logger.Printf(\"%s[req %d] bypass active, passthrough%s\", colorDim, reqIdx, colorReset)\n\t\tbody, err := io.ReadAll(r.Body)\n\t\tr.Body.Close()\n\t\tif err != nil {\n\t\t\thttp.Error(w, \"failed to read request body\", http.StatusBadGateway)\n\t\t\treturn\n\t\t}\n\t\ts.forwardRaw(w, r, body)\n\t\treturn\n\t}\n\n\tbody, err := io.ReadAll(r.Body)\n\tr.Body.Close()\n\tif err != nil {\n\t\ts.logger.Printf(\"[req %d] read body error: %v\", reqIdx, err)\n\t\thttp.Error(w, \"failed to read request body\", http.StatusBadGateway)\n\t\treturn\n\t}\n\n\t// Parse JSON\n\tvar req map[string]any\n\tif err := json.Unmarshal(body, &req); err != nil {\n\t\ts.logger.Printf(\"%s[req %d] JSON parse error, passing through: %v%s\", colorDim, reqIdx, err, colorReset)\n\t\ts.forwardRaw(w, r, body)\n\t\treturn\n\t}\n\n\tmessages, ok := req[\"messages\"].([]any)\n\tif !ok {\n\t\ts.forwardRaw(w, r, body)\n\t\treturn\n\t}\n\n\treq[\"messages\"] = messages\n\n\t// Count ALL raw messages BEFORE sawtooth — this gives the correct total msg:N\n\t// for the entire session (CC sends all messages, proxy compacts later).\n\trawMsgCount := len(messages)\n\n\t// Use session_id directly as thread ID (unique per CC session).\n\t// Prefer X-Claude-Code-Session-Id header (CC v2.1.86+), fallback to body metadata.\n\tthreadID := extractSessionID(req, r.Header.Get(\"X-Claude-Code-Session-Id\"), \"\")\n\tif threadID == \"\" {\n\t\tthreadID = DeriveThreadID(req)\n\t}\n\tproj := extractProjectName(req)\n\tmodel, _ := req[\"model\"].(string)\n\n\t// Persist current model under session-keyed proxy_state so handleWhoami\n\t// can return it. Best-effort: failure is non-fatal (log only).\n\tif threadID != \"\" && model != \"\" {\n\t\tif _, err := s.queryDaemon(\"set_proxy_state\", map[string]any{\n\t\t\t\"key\": \"session_model:\" + threadID,\n\t\t\t\"value\": model,\n\t\t}); err != nil {\n\t\t\ts.logger.Printf(\"[whoami-model] persist failed for %s: %v\", threadID, err)\n\t\t}\n\t}\n\n\t// Refresh runtime config overrides from daemon (per-session or global)\n\ts.refreshConfigOverrides(threadID)\n\n\t// Track whether req has been modified and needs re-serialization\n\tneedsReserialization := false\n\n\t// Message count at request entry (used for actual-based token estimation)\n\tmsgCount := len(messages)\n\n\t// Log message count + cache breakpoint positions for diagnostics\n\ts.logger.Printf(\"req %d: %d messages total\", reqIdx, msgCount)\n\tlogCacheBreakpointLocations(req, s.logger)\n\n\t// Pre-modification dump: capture the raw request before any proxy changes.\n\tif os.Getenv(\"YESMEM_PROXY_DEBUG\") == \"1\" {\n\t\tif preBody, err := json.Marshal(req); err == nil {\n\t\t\tdumpDir := filepath.Join(s.cfg.DataDir, \"debug\")\n\t\t\tos.MkdirAll(dumpDir, 0755)\n\t\t\tts := time.Now().Format(\"20060102-150405\")\n\t\t\tdumpPath := fmt.Sprintf(\"%s/req_%s_%d_pre.json\", dumpDir, ts, reqIdx)\n\t\t\tos.WriteFile(dumpPath, preBody, 0644)\n\t\t\ts.logger.Printf(\"[req %d] pre-dump %dk to %s\", reqIdx, len(preBody)/1024, dumpPath)\n\t\t}\n\t}\n\n\t// Cache bug mitigations (CC Bun fork issues):\n\t// Bug 1: Normalize cch= sentinel patterns in message content to prevent\n\t// Bun's string replacement from corrupting the wrong occurrence.\n\tif SanitizeBillingSentinel(messages) {\n\t\ts.logger.Printf(\"[req %d] CACHE: sanitized billing sentinel in message content\", reqIdx)\n\t\tneedsReserialization = true\n\t}\n\t// Bug 2: Normalize system[0] billing hash to a fixed value so the cache\n\t// prefix stays stable across fresh/resume session boundaries.\n\tif NormalizeBillingHeader(req) {\n\t\ts.logger.Printf(\"[req %d] CACHE: normalized billing header cch= hash\", reqIdx)\n\t\tneedsReserialization = true\n\t}\n\n\t// Profile-aware prompt flags: Claude profile merges shared_prompt + claude_prompt + legacy flat fields.\n\tpfInject := s.getPromptFlags(models.ProfileClaude)\n\n\t// CC custom system prompt replacement: must run before strip/rewrite so replaced blocks\n\t// don't trigger false-positive strip/rewrite calls.\n\ts.applyCCSystemPrompt(req)\n\n\t// Strip role:system messages unconditionally — must run on every request.\n\t// StripReminders (inside runStubCycle) only runs on long conversations.\n\tif filtered := FilterSystemRoleMessages(messages); len(filtered) != len(messages) {\n\t\tmessages = filtered\n\t\treq[\"messages\"] = messages\n\t\tneedsReserialization = true\n\t}\n\n\t// prompt_ungate: strip the CLAUDE.md subordination disclaimer so user instructions carry full authority.\n\tif pfInject.Ungate {\n\t\tif StripCLAUDEMDDisclaimer(req) {\n\t\t\ts.logger.Printf(\"[req %d] SYSTEM: stripped CLAUDE.md disclaimer\", reqIdx)\n\t\t\tneedsReserialization = true\n\t\t}\n\t}\n\n\t// prompt_rewrite: strip output-throttling directives, rewrite quality caps, inject Ant-quality directives\n\tif pfInject.Rewrite {\n\t\t// When CC custom system prompt is active, the system prompt has been replaced with SYSTEM.md.\n\t\t// Strip/rewrite calls would not find their target strings — skip to avoid log noise.\n\t\tif !s.cfg.CustomSystemPrompt.EnabledClaudeCode {\n\t\t\tuserAgent := r.Header.Get(\"User-Agent\")\n\t\t\tif StripOutputEfficiency(req) {\n\t\t\t\ts.logger.Printf(\"[req %d] REWRITE: stripped Output efficiency section\", reqIdx)\n\t\t\t\tneedsReserialization = true\n\t\t\t} else {\n\t\t\t\ts.logRewriteMiss(\"StripOutputEfficiency\", userAgent)\n\t\t\t}\n\t\t\tif StripToneBrevity(req) {\n\t\t\t\ts.logger.Printf(\"[req %d] REWRITE: stripped 'short and concise' from Tone\", reqIdx)\n\t\t\t\tneedsReserialization = true\n\t\t\t} else {\n\t\t\t\ts.logRewriteMiss(\"StripToneBrevity\", userAgent)\n\t\t\t}\n\t\t\tif RewriteGoldPlating(req) {\n\t\t\t\ts.logger.Printf(\"[req %d] REWRITE: rewritten gold-plating directive\", reqIdx)\n\t\t\t\tneedsReserialization = true\n\t\t\t}\n\t\t\tif RewriteErrorHandling(req) {\n\t\t\t\ts.logger.Printf(\"[req %d] REWRITE: rewritten error handling directive\", reqIdx)\n\t\t\t\tneedsReserialization = true\n\t\t\t}\n\t\t\tif RewriteThreeLinesRule(req) {\n\t\t\t\ts.logger.Printf(\"[req %d] REWRITE: rewritten three-lines rule\", reqIdx)\n\t\t\t\tneedsReserialization = true\n\t\t\t}\n\t\t\tif RewriteSubagentCompleteness(req) {\n\t\t\t\ts.logger.Printf(\"[req %d] REWRITE: rewritten subagent completeness\", reqIdx)\n\t\t\t\tneedsReserialization = true\n\t\t\t}\n\t\t\tif RewriteExploreAgentSpeed(req) {\n\t\t\t\ts.logger.Printf(\"[req %d] REWRITE: rewritten explore agent speed bias\", reqIdx)\n\t\t\t\tneedsReserialization = true\n\t\t\t}\n\t\t\tif RewriteSubagentCodeSuppression(req) {\n\t\t\t\ts.logger.Printf(\"[req %d] REWRITE: rewritten subagent code suppression\", reqIdx)\n\t\t\t\tneedsReserialization = true\n\t\t\t}\n\t\t\tif RewriteScopeMatching(req) {\n\t\t\t\ts.logger.Printf(\"[req %d] REWRITE: rewritten scope matching\", reqIdx)\n\t\t\t\tneedsReserialization = true\n\t\t\t}\n\t\t} // end !EnabledClaudeCode guard\n\t\tInjectAntDirectives(req)\n\t\ts.logger.Printf(\"[req %d] REWRITE: injected Ant-quality directives\", reqIdx)\n\t\tneedsReserialization = true\n\t}\n\n\t// prompt_enhance: CLAUDE.md authority + comment discipline + persona tone\n\tif pfInject.Enhance {\n\t\tInjectCLAUDEMDAuthority(req)\n\t\tif raw, err := s.queryDaemon(\"get_persona\", nil); err == nil {\n\t\t\tvar persona map[string]any\n\t\t\tif json.Unmarshal(raw, &persona) == nil {\n\t\t\t\tif traits, ok := persona[\"traits\"].(map[string]any); ok {\n\t\t\t\t\tif verbosity, ok := traits[\"verbosity\"].(string); ok {\n\t\t\t\t\t\tInjectPersonaTone(req, verbosity)\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\ts.logger.Printf(\"[req %d] ENHANCE: injected authority + persona tone\", reqIdx)\n\t\tneedsReserialization = true\n\t}\n\n\t// yesmem directive blocks: restore guidance Anthropic dropped 2026-03→04.\n\t// Profile-aware via getPromptFlags(ProfileClaude) — shared + claude layers merged.\n\tpf := s.getPromptFlags(models.ProfileClaude)\n\tif pf.ToolPrefs || pf.OutputDiscipline || pf.CodingDiscipline ||\n\t\tpf.Beweislast || pf.ScopeDiscipline || pf.DelegationContract ||\n\t\tpf.ClarifyFirst {\n\t\tif pf.ToolPrefs {\n\t\t\tInjectClaudeToolPrefs(req)\n\t\t}\n\t\tif pf.OutputDiscipline {\n\t\t\tInjectOutputDiscipline(req)\n\t\t}\n\t\tif pf.CodingDiscipline {\n\t\t\tInjectCodingDiscipline(req)\n\t\t}\n\t\tif pf.Beweislast {\n\t\t\tInjectBeweislast(req)\n\t\t}\n\t\tif pf.Fable {\n\t\t\tInjectFable(req)\n\t\t}\n\t\tif pf.ScopeDiscipline {\n\t\t\tInjectScopeDiscipline(req)\n\t\t}\n\t\tif pf.DelegationContract {\n\t\t\tInjectDelegationContract(req)\n\t\t}\n\t\tif pf.ClarifyFirst {\n\t\t\tInjectClarifyFirst(req)\n\t\t}\n\t\tif pf.CodeToolsFirst {\n\t\t\tInjectCodeToolsFirst(req, proj)\n\t\t}\n\t\tif pf.WikiFirst {\n\t\t\tInjectWikiFirst(req, proj)\n\t\t}\n\t\tneedsReserialization = true\n\t\ts.logger.Printf(\"[req %d] DIRECTIVES: injected yesmem-* discipline blocks\", reqIdx)\n\t}\n\n\tif s.cfg.PromptPatternSuggest && proj != \"\" {\n\t\tpatternMsgs, _ := req[\"messages\"].([]any)\n\t\tdetectReplPatternSuggestion(patternMsgs, proj, s.queryDaemon)\n\t}\n\n\t// Turn-sequence recording: hash tool types from previous assistant turn, send to daemon\n\tif s.cfg.PromptPatternSuggest && proj != \"\" && threadID != \"\" {\n\t\tif msgs, ok := req[\"messages\"].([]any); ok {\n\t\t\tif turnHash, toolNames := computeTurnHashFromMessages(msgs); turnHash != \"\" {\n\t\t\t\tgo s.queryDaemon(\"record_turn_sequence\", map[string]any{\n\t\t\t\t\t\"thread_id\": threadID,\n\t\t\t\t\t\"project\": proj,\n\t\t\t\t\t\"turn_hash\": turnHash,\n\t\t\t\t\t\"example_tools\": toolNames,\n\t\t\t\t})\n\t\t\t}\n\t\t}\n\t}\n\n\t// thinking normalization: convert thinking.type \"enabled\" → \"adaptive\" for models that require it\n\tif NormalizeThinkingType(req) {\n\t\ts.logger.Printf(\"[req %d] THINKING: normalized type=enabled → adaptive for %s\", reqIdx, model)\n\t\tneedsReserialization = true\n\t}\n\n\t// effort_floor: ensure output_config.effort is at least the configured minimum\n\tif s.cfg.EffortFloor != \"\" {\n\t\tif EnforceEffortFloor(req, s.cfg.EffortFloor) {\n\t\t\ts.logger.Printf(\"[req %d] EFFORT: enforced floor=%s\", reqIdx, s.cfg.EffortFloor)\n\t\t\tneedsReserialization = true\n\t\t}\n\t}\n\n\t// Inject identity hint on first message of a thread so Claude knows the briefing is from itself.\n\tif len(messages) <= 1 {\n\t\tAppendSystemBlock(req, \"yesmem-identity\", \"The first user message contains your briefing — written by yourself, from your memory. This is not an external document. This is you, remembering.\")\n\t\tneedsReserialization = true\n\t}\n\n\t// Token estimation: actual-based (lastActual + delta) or full count fallback\n\toverhead := s.measureOverhead(req)\n\ttotalTokens := s.estimateTotalTokens(threadID, messages, overhead)\n\n\t// Task #7: Retry detection — skip side-effects on retries\n\tfp := requestFingerprint(messages)\n\tisRetryReq := s.isRetry(fp)\n\tif isRetryReq {\n\t\ts.logger.Printf(\"%s[req %d %s tid=%s] retry detected, skipping side-effects%s\", colorOrange, reqIdx, proj, threadID, colorReset)\n\t}\n\n\t// Increment project turn counter (async, non-blocking, skip on retries)\n\tif !isRetryReq {\n\t\tturnProject := proj\n\t\tif turnProject == \"\" {\n\t\t\tturnProject = \"__global__\"\n\t\t}\n\t\tgo s.queryDaemon(\"increment_turn\", map[string]any{\"project\": turnProject})\n\t}\n\n\t// Extract user query once (used by re-expansion and associative retrieval)\n\tuserQuery := lastUserText(messages)\n\n\t// Extract tool_use_ids for post-hoc annotation (passed as parameter, no shared state)\n\ttoolUseIDs := extractToolUseIDs(messages)\n\n\t// === Inline Reflection: scan previous assistant response for signals ===\n\ts.lastInjectedIDsMu.Lock()\n\tprevInjected := s.lastInjectedIDs[threadID]\n\ts.lastInjectedIDsMu.Unlock()\n\n\tif len(prevInjected) > 0 && !isRetryReq {\n\t\tsignals := scanAssistantSignals(messages)\n\n\t\t// Signal 1: Learning Used — use_count (all sources)\n\t\tvar confirmedUsed []int64\n\t\tfor _, id := range signals.UsedIDs {\n\t\t\tif _, ok := prevInjected[id]; ok {\n\t\t\t\tconfirmedUsed = append(confirmedUsed, id)\n\t\t\t}\n\t\t}\n\t\tif len(confirmedUsed) > 0 {\n\t\t\tidsAny := make([]any, len(confirmedUsed))\n\t\t\tfor i, id := range confirmedUsed {\n\t\t\t\tidsAny[i] = id\n\t\t\t}\n\t\t\tgo s.queryDaemon(\"increment_use\", map[string]any{\"ids\": idsAny})\n\t\t}\n\n\t\t// Signal 2: Noise — only for associative and fresh injections.\n\t\t// Briefing learnings are injected every turn, so \"not used this turn\"\n\t\t// is expected and not noise. Only contextual injections that were\n\t\t// ignored are real noise signals.\n\t\tusedSet := make(map[int64]bool, len(confirmedUsed))\n\t\tfor _, id := range confirmedUsed {\n\t\t\tusedSet[id] = true\n\t\t}\n\t\tvar noiseIDs []int64\n\t\tfor id, source := range prevInjected {\n\t\t\tif usedSet[id] {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tif source == \"associative\" || source == \"fresh\" {\n\t\t\t\tnoiseIDs = append(noiseIDs, id)\n\t\t\t}\n\t\t\t// briefing: skip — not being used in one turn is expected\n\t\t}\n\t\tif len(noiseIDs) > 0 {\n\t\t\tidsAny := make([]any, len(noiseIDs))\n\t\t\tfor i, id := range noiseIDs {\n\t\t\t\tidsAny[i] = id\n\t\t\t}\n\t\t\tgo s.queryDaemon(\"increment_noise\", map[string]any{\"ids\": idsAny})\n\t\t}\n\n\t\t// Signal 3: Error Feedback — fail_count on used learnings when tool errors occurred.\n\t\t// If Claude referenced a learning and the resulting action still failed,\n\t\t// the learning didn't prevent the error. Only bump on USED ids (not all injected).\n\t\tif signals.HasToolErrors && len(confirmedUsed) > 0 {\n\t\t\tfailAny := make([]any, len(confirmedUsed))\n\t\t\tfor i, id := range confirmedUsed {\n\t\t\t\tfailAny[i] = id\n\t\t\t}\n\t\t\tgo s.queryDaemon(\"increment_fail\", map[string]any{\"ids\": failAny})\n\t\t\tif s.logger != nil {\n\t\t\t\ts.logger.Printf(\"[inline-reflection] error feedback: %d used learnings → fail_count++\", len(confirmedUsed))\n\t\t\t}\n\t\t}\n\n\t\t// Signal 4: Knowledge Gaps\n\t\tfor _, topic := range signals.GapTopics {\n\t\t\tgo s.queryDaemon(\"track_gap\", map[string]any{\"topic\": topic, \"project\": proj})\n\t\t}\n\n\t\t// Signal 3: Contradictions\n\t\tfor _, desc := range signals.Contradictions {\n\t\t\tgo s.queryDaemon(\"flag_contradiction\", map[string]any{\"description\": desc, \"project\": proj, \"thread_id\": threadID})\n\t\t}\n\n\t\tif len(confirmedUsed) > 0 || len(signals.GapTopics) > 0 || len(signals.Contradictions) > 0 {\n\t\t\ts.logger.Printf(\"[req %d %s tid=%s] inline-reflection: injected=%d used=%d noise=%d gaps=%d contradictions=%d\",\n\t\t\t\treqIdx, proj, threadID, len(prevInjected), len(confirmedUsed), len(noiseIDs), len(signals.GapTopics), len(signals.Contradictions))\n\t\t}\n\t}\n\n\t// Commit detection: check for git commit in tool_result, trigger async stale-learning evaluation\n\tif ci := detectGitCommit(messages); ci != nil {\n\t\tworkdir := extractWorkingDirectory(req)\n\t\ts.logger.Printf(\"[req %d %s tid=%s] git commit detected: %s %s\", reqIdx, proj, threadID, ci.Hash, ci.Message)\n\t\tgo s.queryDaemon(\"invalidate_on_commit\", map[string]any{\n\t\t\t\"hash\": ci.Hash, \"project\": proj, \"workdir\": workdir,\n\t\t})\n\t}\n\n\t// Loop detection: check for repeating tool-call patterns and inject warning\n\tif threadID != \"\" && !isRetryReq {\n\t\ts.loopMu.Lock()\n\t\tloopState := s.loopStates[threadID]\n\t\tif loopState == nil {\n\t\t\tloopState = &LoopState{}\n\t\t\ts.loopStates[threadID] = loopState\n\t\t}\n\t\ts.loopMu.Unlock()\n\n\t\tif warning, level := CheckLoopAndFormat(messages, loopState); warning != \"\" {\n\t\t\tAppendSystemBlock(req, \"yesmem-loop-warning\", warning)\n\t\t\tneedsReserialization = true\n\t\t\ts.logger.Printf(\"%s[req %d %s tid=%s] LOOP: level %d warning injected%s\", colorOrange, reqIdx, proj, threadID, level, colorReset)\n\t\t}\n\t}\n\n\t// Task #4: Subagent detection — skip all proxy logic for short conversations\n\tif isSubagent(messages, req) {\n\t\t// Inject docs hint for subagents if reference docs are indexed\n\t\tif hint := s.getDocsHint(proj); hint != \"\" {\n\t\t\tmodified := injectDocsHintForSubagent(messages, hint)\n\t\t\treq[\"messages\"] = modified\n\t\t\tif newBody, err := json.Marshal(req); err == nil {\n\t\t\t\tbody = newBody\n\t\t\t}\n\t\t\ts.logger.Printf(\"%s[req %d %s tid=%s] %d messages (subagent, docs-hint injected)%s\", colorOrange, reqIdx, proj, threadID, len(messages), colorReset)\n\t\t} else {\n\t\t\ts.logger.Printf(\"%s[req %d %s tid=%s] %d messages (subagent, passthrough)%s\", colorOrange, reqIdx, proj, threadID, len(messages), colorReset)\n\t\t}\n\t\ts.forwardWithAnnotation(w, r, body, reqIdx, toolUseIDs, proj, threadID, msgCount)\n\t\treturn\n\t}\n\n\t// === Sawtooth Cache Optimization ===\n\t// When enabled: use frozen stubs between stub-cycles for cache hits.\n\t// When disabled: fall back to progressive decay on every request above threshold.\n\n\tsawtoothCutoff := 0 // raw index where fresh tail starts (0 = no frozen stubs)\n\tsawtoothStubs := 0 // number of frozen stubs in combined array\n\n\tif s.cfg.SawtoothEnabled {\n\t\t// Restore decay state from DB on cold start (before frozen stubs use it)\n\t\ts.decay.LoadFromDB(threadID)\n\n\t\t// Check for existing frozen stubs first\n\t\tfrozen := s.frozenStubs.Get(threadID, messages)\n\t\tif frozen != nil {\n\t\t\tsawtoothCutoff = frozen.Cutoff\n\t\t\tsawtoothStubs = len(frozen.Messages)\n\t\t\t// Estimate combined token count: frozen prefix + fresh tail + overhead\n\t\t\tfreshMessages := messages[frozen.Cutoff:]\n\t\t\tfreshTokens := s.countMessageTokens(freshMessages)\n\t\t\tcombinedTokens := frozen.Tokens + freshTokens + overhead\n\n\t\t\tif shouldInvalidateFrozen(totalTokens, s.effectiveTokenThreshold(model)) {\n\t\t\t\t// Fresh tail grew too large — invalidate and re-stub\n\t\t\t\ts.logger.Printf(\"[req %d %s tid=%s] SAWTOOTH: frozen prefix expired (totalTokens=%dk > %dk threshold, combined=%dk: %dk frozen + %dk fresh + %dk overhead)\",\n\t\t\t\t\treqIdx, proj, threadID, totalTokens/1000, s.effectiveTokenThreshold(model)/1000, combinedTokens/1000, frozen.Tokens/1000, freshTokens/1000, overhead/1000)\n\t\t\t\ts.invalidateThreadCaches(threadID, proj, extractWorkingDirectory(req))\n\t\t\t\tfrozen = nil // fall through to trigger check below\n\t\t\t} else {\n\t\t\t\t// Use frozen prefix + fresh tail\n\t\t\t\tcombined := make([]any, 0, len(frozen.Messages)+len(freshMessages))\n\t\t\t\tcombined = append(combined, frozen.Messages...)\n\t\t\t\tcombined = append(combined, freshMessages...)\n\t\t\t\t// Eager-stub large tool_results in fresh tail (model already processed them)\n\t\t\t\tbeforeEager := s.countMessageTokens(combined[len(frozen.Messages):])\n\t\t\t\tvar stubSticky, stubFresh int\n\t\t\t\tcombined = EagerStubToolResults(combined, len(frozen.Messages), s.countTokens, WithStubMemory(s.eagerStubMemory, threadID), WithStubCounters(&stubSticky, &stubFresh))\n\t\t\t\tafterEager := s.countMessageTokens(combined[len(frozen.Messages):])\n\t\t\t\tif beforeEager != afterEager {\n\t\t\t\t\ts.logger.Printf(\"[req %d %s tid=%s] EAGER-STUB: fresh %dk → %dk (saved %dk) [sticky=%d fresh=%d]\", reqIdx, proj, threadID, beforeEager/1000, afterEager/1000, (beforeEager-afterEager)/1000, stubSticky, stubFresh)\n\t\t\t\t}\n\t\t\t\treq[\"messages\"] = combined\n\t\t\t\tneedsReserialization = true\n\t\t\t\t// Strip embedded CC breakpoints from frozen stubs — they waste cache slots.\n\t\t\t\t// InjectFrozenStubCacheBreakpoint adds exactly one below.\n\t\t\t\tif n := StripMessagesCacheControl(req, 0, len(frozen.Messages)); n > 0 {\n\t\t\t\t\ts.logger.Printf(\"[req %d %s tid=%s] SAWTOOTH: stripped %d embedded breakpoints from frozen stubs\", reqIdx, proj, threadID, n)\n\t\t\t\t}\n\t\t\t\ts.logger.Printf(\"%s[req %d %s tid=%s] SAWTOOTH: frozen prefix (%d stubs, %dk msg) + %d fresh (%dk msg) + %dk overhead = %dk total%s\",\n\t\t\t\t\tcolorGreen, reqIdx, proj, threadID, len(frozen.Messages), frozen.Tokens/1000, len(freshMessages), freshTokens/1000, overhead/1000, combinedTokens/1000, colorReset)\n\t\t\t\trawMsgTokens := s.countMessageTokens(messages)\n\t\t\t\ts.setRawEstimate(threadID, rawMsgTokens+overhead)\n\t\t\t\ts.logger.Printf(\"[req %d %s tid=%s] RAW ESTIMATE: msgTokens=%dk + overhead=%dk = %dk (messages=%d)\", reqIdx, proj, threadID, rawMsgTokens/1000, overhead/1000, (rawMsgTokens+overhead)/1000, len(messages))\n\t\t\t\tif InjectFrozenStubCacheBreakpoint(req, len(frozen.Messages)) {\n\t\t\t\t\ts.logger.Printf(\"[req %d %s tid=%s] SAWTOOTH: frozen stub cache breakpoint at messages[%d]\",\n\t\t\t\t\t\treqIdx, proj, threadID, len(frozen.Messages)-1)\n\t\t\t\t}\n\n\t\t\t\t// Ensure frozen stubs are persisted (may have failed on first request after deploy)\n\t\t\t\ts.ensureFrozenPersisted(threadID)\n\t\t\t}\n\t\t}\n\t\tif frozen == nil {\n\t\t\t// No frozen stubs — check trigger\n\t\t\ttriggerReason := s.sawtoothTrigger.ShouldTrigger(threadID, totalTokens)\n\t\t\tif triggerReason != TriggerNone {\n\t\t\t\ts.logger.Printf(\"[req %d %s tid=%s] SAWTOOTH stub-cycle triggered: %s (tokens=%dk)\",\n\t\t\t\t\treqIdx, proj, threadID, triggerReason, totalTokens/1000)\n\n\t\t\t\t// Refresh briefing during stub-cycle — disabled (briefing now via SessionStart hook)\n\t\t\t\t// s.refreshBriefing(proj)\n\t\t\t\t// if s.briefingText != \"\" {\n\t\t\t\t// \tUpsertSystemBlockCached(req, \"yesmem-briefing\", s.briefingText)\n\t\t\t\t// }\n\n\t\t\t\t// Run full stub pipeline\n\t\t\t\ts.setRawEstimate(threadID, totalTokens)\n\t\t\t\t_ = s.runStubCycle(messages, req, reqIdx, proj, threadID, overhead, totalTokens, userQuery, isRetryReq)\n\n\t\t\t\t// Freeze the result\n\t\t\t\tfinalMessages, _ := req[\"messages\"].([]any)\n\t\t\t\tif finalMessages != nil && len(finalMessages) > 0 {\n\t\t\t\t\t// Boundary = last message of original that maps to last stub\n\t\t\t\t\tcutoff := len(messages)\n\t\t\t\t\tsawtoothCutoff = cutoff\n\t\t\t\t\tvar boundaryMsg any\n\t\t\t\t\tif cutoff > 0 {\n\t\t\t\t\t\tboundaryMsg = messages[cutoff-1]\n\t\t\t\t\t}\n\t\t\t\t\tfrozenTokens := s.countMessageTokens(finalMessages)\n\t\t\t\t\tfrozenCount, stripped, breakpointInjected := s.freezeStubsAndInjectBreakpoint(req, threadID, cutoff, boundaryMsg, frozenTokens, totalTokens)\n\t\t\t\t\ts.decay.Persist(threadID)\n\t\t\t\t\tif stripped > 0 {\n\t\t\t\t\t\ts.logger.Printf(\"[req %d %s tid=%s] SAWTOOTH: stripped %d embedded breakpoints before freeze\",\n\t\t\t\t\t\t\treqIdx, proj, threadID, stripped)\n\t\t\t\t\t}\n\t\t\t\t\ts.logger.Printf(\"[req %d %s tid=%s] SAWTOOTH: frozen %d messages at cutoff=%d (~%dk tokens)\",\n\t\t\t\t\t\treqIdx, proj, threadID, frozenCount, cutoff, frozenTokens/1000)\n\t\t\t\t\tif breakpointInjected {\n\t\t\t\t\t\ts.logger.Printf(\"[req %d %s tid=%s] SAWTOOTH: frozen stub cache breakpoint at messages[%d]\",\n\t\t\t\t\t\t\treqIdx, proj, threadID, frozenCount-1)\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tneedsReserialization = true\n\t\t\t} else {\n\t\t\t\t// No trigger, no frozen stubs — eager-stub to delay first collapse\n\t\t\t\tbeforeEager := s.countMessageTokens(messages)\n\t\t\t\tvar stubSticky, stubFresh int\n\t\t\t\tmessages = EagerStubToolResults(messages, 0, s.countTokens, WithStubMemory(s.eagerStubMemory, threadID), WithStubCounters(&stubSticky, &stubFresh))\n\t\t\t\tafterEager := s.countMessageTokens(messages)\n\t\t\t\tif beforeEager != afterEager {\n\t\t\t\t\ts.logger.Printf(\"[req %d %s tid=%s] EAGER-STUB: %dk → %dk (saved %dk) [sticky=%d fresh=%d]\", reqIdx, proj, threadID, beforeEager/1000, afterEager/1000, (beforeEager-afterEager)/1000, stubSticky, stubFresh)\n\t\t\t\t\treq[\"messages\"] = messages\n\t\t\t\t\tneedsReserialization = true\n\t\t\t\t}\n\t\t\t\ts.logger.Printf(\"%s[req %d %s tid=%s] %d messages, ~%dk tokens (sawtooth: no trigger)%s\",\n\t\t\t\t\tcolorDim, reqIdx, proj, threadID, len(messages), totalTokens/1000, colorReset)\n\t\t\t}\n\t\t}\n\t} else {\n\t\t// Legacy path: progressive decay\n\t\tif s.shouldStub(totalTokens, model) {\n\t\t\ts.setRawEstimate(threadID, totalTokens)\n\t\t\t_ = s.runStubCycle(messages, req, reqIdx, proj, threadID, overhead, totalTokens, userQuery, isRetryReq)\n\t\t\tneedsReserialization = true\n\t\t}\n\t}\n\n\t// Persistent timestamp injection: re-injects stored timestamps on ALL previous user messages.\n\t// msg:N is computed from position (counting user messages in raw array), NOT from a counter.\n\t// The injected data is deterministic (same bytes every turn) = cache-safe after initial miss.\n\tif threadID != \"\" {\n\t\tif currentMsgs, ok := req[\"messages\"].([]any); ok && len(currentMsgs) > 0 {\n\t\t\ts.timestampStore.Load(threadID)\n\n\t\t\t// Offset = cutoff - stubsCount: maps combined array index to raw position.\n\t\t\t// Raw position of message at combined index I = cutoff + (I - stubs).\n\t\t\t// msgN = rawPos + 1 = cutoff + (I - stubs) + 1 = (cutoff - stubs) + I + 1.\n\t\t\tmsgOffset := sawtoothCutoff - sawtoothStubs\n\n\t\t\ts.logger.Printf(\"[req %d %s] timestamps: rawTotal=%d, postSawtooth=%d, cutoff=%d, stubs=%d, offset=%d\", reqIdx, proj, rawMsgCount, len(currentMsgs), sawtoothCutoff, sawtoothStubs, msgOffset)\n\n\t\t\t// Pre-store assistant timestamp BEFORE inject loop so it's available immediately\n\t\t\ts.responseTsMu.RLock()\n\t\t\trespTime, hasRespTime := s.responseTimes[threadID]\n\t\t\ts.responseTsMu.RUnlock()\n\t\t\tif hasRespTime {\n\t\t\t\trespStr := shortWeekday(respTime.Weekday()) + \" \" + respTime.Format(\"2006-01-02 15:04:05\")\n\t\t\t\tfor j := len(currentMsgs) - 2; j >= 0; j-- {\n\t\t\t\t\tprevMsg, _ := currentMsgs[j].(map[string]any)\n\t\t\t\t\tif prevMsg == nil || prevMsg[\"role\"] != \"assistant\" {\n\t\t\t\t\t\tcontinue\n\t\t\t\t\t}\n\t\t\t\t\tif !msgHasTextBlock(prevMsg) {\n\t\t\t\t\t\ts.logger.Printf(\"[req %d %s] timestamps: skip assistant[%d] no text block\", reqIdx, proj, j)\n\t\t\t\t\t\tcontinue // skip tool-use-only assistant messages\n\t\t\t\t\t}\n\t\t\t\t\tprevMsgN := msgOffset + j + 1\n\t\t\t\t\tif _, exists := s.timestampStore.Get(threadID, prevMsgN); !exists {\n\t\t\t\t\t\ts.timestampStore.Store(threadID, prevMsgN, &TimestampMeta{Timestamp: respStr})\n\t\t\t\t\t\ts.logger.Printf(\"[req %d %s] timestamps: stored assistant msg:%d ts=%s\", reqIdx, proj, prevMsgN, respStr)\n\t\t\t\t\t} else {\n\t\t\t\t\t\ts.logger.Printf(\"[req %d %s] timestamps: assistant msg:%d already has ts\", reqIdx, proj, prevMsgN)\n\t\t\t\t\t}\n\t\t\t\t\tbreak\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\ts.logger.Printf(\"[req %d %s] timestamps: no responseTimes for thread\", reqIdx, proj)\n\t\t\t}\n\n\t\t\t// Inject [msg:N] (+ timestamp/delta if stored) on all PREVIOUS messages\n\t\t\tif isFeatureEnabled(&s.cfg, model, \"timestamps\") {\n\t\t\t\tif n := InjectTimestamps(s.timestampStore, threadID, currentMsgs, len(currentMsgs)-1, msgOffset, sawtoothStubs); n > 0 {\n\t\t\t\t\tneedsReserialization = true\n\t\t\t\t\ts.logger.Printf(\"[req %d %s] timestamps: injected %d\", reqIdx, proj, n)\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t// Current (last) user message: full timestamp + delta + msg:N from raw count\n\t\t\tlastMsg, _ := currentMsgs[len(currentMsgs)-1].(map[string]any)\n\t\t\tif lastMsg != nil && lastMsg[\"role\"] == \"user\" && !msgHasMetaPrefix(lastMsg) {\n\t\t\t\tnow := time.Now()\n\t\t\t\tnowStr := shortWeekday(now.Weekday()) + \" \" + now.Format(\"2006-01-02 15:04:05\")\n\t\t\t\tentry := &TimestampMeta{Timestamp: nowStr}\n\t\t\t\tif hasRespTime {\n\t\t\t\t\tentry.Delta = formatDelta(now.Sub(respTime))\n\t\t\t\t}\n\t\t\t\ts.timestampStore.Store(threadID, rawMsgCount, entry)\n\t\t\t\tif isFeatureEnabled(&s.cfg, model, \"timestamps\") {\n\t\t\t\t\tprependMeta(lastMsg, BuildMeta(rawMsgCount, entry))\n\t\t\t\t}\n\t\t\t\tneedsReserialization = true\n\t\t\t}\n\n\t\t\ts.timestampStore.Persist(threadID)\n\t\t}\n\t}\n\n\t// Associative context injection (for ALL requests, not just stubbed ones)\n\t// Re-enabled with higher thresholds (55/75, max 1) after disabling Fresh Remember.\n\tassocContext := s.findAssociativeContextFor(userQuery, proj, threadID, messages)\n\tif assocContext != \"\" {\n\t\tcurrentMessages, _ := req[\"messages\"].([]any)\n\t\tif currentMessages == nil {\n\t\t\tcurrentMessages = messages\n\t\t}\n\t\treq[\"messages\"] = injectAssociativeContext(currentMessages, assocContext, s.cfg.SawtoothEnabled)\n\t\tneedsReserialization = true\n\t\ts.logger.Printf(\"%s[req %d %s tid=%s] associative context injected%s\", colorBlue, reqIdx, proj, threadID, colorReset)\n\t}\n\n\t// Doc-chunk context injection: separate search path for indexed docs (not skills)\n\tdocContext := s.findDocContextFor(userQuery, proj, messages)\n\tif docContext != \"\" {\n\t\tcurrentMessages, _ := req[\"messages\"].([]any)\n\t\tif currentMessages == nil {\n\t\t\tcurrentMessages = messages\n\t\t}\n\t\treq[\"messages\"] = injectAssociativeContext(currentMessages, docContext, s.cfg.SawtoothEnabled)\n\t\tneedsReserialization = true\n\t\ts.logger.Printf(\"%s[req %d %s tid=%s] doc context injected%s\", colorBlue, reqIdx, proj, threadID, colorReset)\n\t}\n\n\t// Fresh Remember Injection: DISABLED\n\t// Causes echo-loop: Claude saves via remember(), sees it again next turn as\n\t// [yesmem fresh memory], reacts to own output instead of user's request.\n\t// Recovery after /clear is handled by recovery.go (briefing), not here.\n\t// Subagents get their own briefing. No remaining use-case.\n\t// remItems := s.popRecentRemember()\n\tvar remItems []recentLearningItem\n\n\t// Rules re-injection: inject condensed CLAUDE.md rules every ~40k output tokens\n\tif rulesBlock := s.rulesInject(threadID, totalTokens, proj); rulesBlock != \"\" {\n\t\tcurrentMessages, _ := req[\"messages\"].([]any)\n\t\tif currentMessages == nil {\n\t\t\tcurrentMessages = messages\n\t\t}\n\t\treq[\"messages\"] = injectAssociativeContext(currentMessages, s.formatRulesReminder(rulesBlock, proj, false), s.cfg.SawtoothEnabled)\n\t\tneedsReserialization = true\n\t\ts.logger.Printf(\"%s[req %d %s tid=%s] rules reminder injected%s\", colorBlue, reqIdx, proj, threadID, colorReset)\n\t}\n\n\t// Plan auto-activation: nudge Claude to call set_plan() when reading a plan file\n\tif planFile := detectPlanFileRead(messages); planFile != \"\" {\n\t\tif activePlan, _ := s.getActivePlan(threadID); shouldNudgePlan(planFile, activePlan) {\n\t\t\tAppendSystemBlock(req, \"plan-nudge\",\n\t\t\t\tfmt.Sprintf(\"Du hast einen Implementierungsplan gelesen (%s). Aktiviere ihn jetzt via set_plan() damit Plan-Checkpoints funktionieren und dein Fortschritt getrackt wird.\", filepath.Base(planFile)))\n\t\t\tneedsReserialization = true\n\t\t\ts.logger.Printf(\"%s[req %d %s tid=%s] plan nudge injected for %s%s\", colorBlue, reqIdx, proj, threadID, filepath.Base(planFile), colorReset)\n\t\t}\n\t}\n\n\t// Plan checkpoint: inject plan reminder every ~20k tokens if active plan exists\n\ts.detectPlanToolCall(messages, threadID, totalTokens)\n\tif checkpoint := s.planCheckpointInject(threadID, totalTokens); checkpoint != \"\" {\n\t\tcurrentMessages, _ := req[\"messages\"].([]any)\n\t\tif currentMessages == nil {\n\t\t\tcurrentMessages = messages\n\t\t}\n\t\treq[\"messages\"] = injectAssociativeContext(currentMessages, checkpoint, s.cfg.SawtoothEnabled)\n\t\tneedsReserialization = true\n\t\ts.logger.Printf(\"%s[req %d %s tid=%s] plan checkpoint injected%s\", colorBlue, reqIdx, proj, threadID, colorReset)\n\t}\n\n\t// Docs hint injection: inject docs-available reminder every ~10k tokens (independent of plan)\n\tif docsHint := s.docsHintInject(threadID, totalTokens, proj); docsHint != \"\" {\n\t\tcurrentMessages, _ := req[\"messages\"].([]any)\n\t\tif currentMessages == nil {\n\t\t\tcurrentMessages = messages\n\t\t}\n\t\treq[\"messages\"] = injectAssociativeContext(currentMessages, docsHint, s.cfg.SawtoothEnabled)\n\t\tneedsReserialization = true\n\t\ts.logger.Printf(\"%s[req %d %s tid=%s] docs hint injected%s\", colorBlue, reqIdx, proj, threadID, colorReset)\n\t}\n\tif len(remItems) > 0 {\n\t\tvar lines []string\n\t\tfor _, item := range remItems {\n\t\t\tif item.ID > 0 {\n\t\t\t\tlines = append(lines, fmt.Sprintf(\"- [ID:%d] %s\", item.ID, truncateStr(item.Text, 200)))\n\t\t\t} else {\n\t\t\t\tlines = append(lines, \"- \"+truncateStr(item.Text, 200))\n\t\t\t}\n\t\t}\n\t\tremContext := fmt.Sprintf(\"[yesmem fresh memory]\\nGerade gelernt:\\n%s\\n%s\\n[/yesmem fresh memory]\", strings.Join(lines, \"\\n\"), InlineReflectionHint)\n\t\tcurrentMessages, _ := req[\"messages\"].([]any)\n\t\tif currentMessages == nil {\n\t\t\tcurrentMessages = messages\n\t\t}\n\t\treq[\"messages\"] = injectAssociativeContext(currentMessages, remContext, s.cfg.SawtoothEnabled)\n\t\tneedsReserialization = true\n\t\ts.logger.Printf(\"%s[req %d %s tid=%s] fresh remember injected (%d items)%s\", colorBlue, reqIdx, proj, threadID, len(remItems), colorReset)\n\t}\n\n\t// Skill eval injection: mandatory evaluation instruction for real user input only\n\tif threadID != \"\" && proj != \"\" && isUserInputTurn(messages) {\n\t\t// First: detect any skill activations from message history (updates tracker)\n\t\ts.syncSkillActivations(messages, proj, threadID)\n\n\t\t// Inject mandatory skill evaluation block into last user message\n\t\tskillEval := buildSkillEvalBlock(s.cfg.SkillEvalInject)\n\t\tcurrentMessages, _ := req[\"messages\"].([]any)\n\t\tif currentMessages == nil {\n\t\t\tcurrentMessages = messages\n\t\t}\n\t\tif skillEval != \"\" {\n\t\t\treq[\"messages\"] = injectAssociativeContext(currentMessages, skillEval, s.cfg.SawtoothEnabled)\n\t\t\tneedsReserialization = true\n\t\t\ts.logger.Printf(\"%s[req %d %s tid=%s] skill-eval injected (mode=%s)%s\", colorBlue, reqIdx, proj, threadID, s.cfg.SkillEvalInject, colorReset)\n\t\t}\n\t}\n\n\t// === Collect injected learning IDs for next turn's inline reflection ===\n\tif threadID != \"\" {\n\t\tcurrentIDs := make(map[int64]string) // id → source\n\t\t// Source 1: Briefing learnings (cached, same every turn)\n\t\tbriefingSnapshot, _, _ := s.getCachedBriefing(threadID, proj)\n\t\tif briefingSnapshot != \"\" {\n\t\t\tfor _, m := range idPattern.FindAllStringSubmatch(briefingSnapshot, -1) {\n\t\t\t\tif id, err := strconv.ParseInt(m[1], 10, 64); err == nil {\n\t\t\t\t\tcurrentIDs[id] = \"briefing\"\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\t// Source 2: Associative context (contextual, per-turn)\n\t\tif assocContext != \"\" {\n\t\t\tfor _, m := range idPattern.FindAllStringSubmatch(assocContext, -1) {\n\t\t\t\tif id, err := strconv.ParseInt(m[1], 10, 64); err == nil {\n\t\t\t\t\tcurrentIDs[id] = \"associative\" // overwrites briefing if both — associative is the stronger signal\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\t// Source 3: Fresh remember items (just learned)\n\t\tfor _, item := range remItems {\n\t\t\tif item.ID > 0 {\n\t\t\t\tcurrentIDs[item.ID] = \"fresh\"\n\t\t\t}\n\t\t}\n\t\ts.lastInjectedIDsMu.Lock()\n\t\tif len(currentIDs) > 0 {\n\t\t\t// Accumulate IDs across turns — don't overwrite.\n\t\t\t// Claude may reference an ID from turn 30 in turn 50.\n\t\t\texisting := s.lastInjectedIDs[threadID]\n\t\t\tif existing == nil {\n\t\t\t\texisting = make(map[int64]string)\n\t\t\t}\n\t\t\tfor id, source := range currentIDs {\n\t\t\t\texisting[id] = source\n\t\t\t}\n\t\t\ts.lastInjectedIDs[threadID] = existing\n\t\t}\n\t\ts.lastInjectedIDsMu.Unlock()\n\t}\n\n\t// Think reminder injection — only for real user turns, not tool continuations\n\tif threadID != \"\" {\n\t\tcurrentMessages, _ := req[\"messages\"].([]any)\n\t\tif currentMessages == nil {\n\t\t\tcurrentMessages = messages\n\t\t}\n\t\tif lastUserHasText(currentMessages) && lastUserTextLen(currentMessages) >= resolveThinkReminderMinChars(&s.cfg, \"\") {\n\t\t\tthinkReminder := s.buildThinkReminder(threadID, threadID, false)\n\t\t\tif thinkReminder != \"\" {\n\t\t\t\treq[\"messages\"] = injectAssociativeContext(currentMessages, thinkReminder, s.cfg.SawtoothEnabled)\n\t\t\t\tneedsReserialization = true\n\t\t\t}\n\t\t}\n\n\t\t// Refresh frozen-prefix snapshot with the post-timestamps bytes that\n\t\t// will appear in the wire body for the frozen range. This MUST run\n\t\t// BEFORE injectBriefingTurn / injectCodeMapTurn / injectCapabilitiesTurn:\n\t\t// those stages prepend or insert turns into req[\"messages\"], which\n\t\t// shifts the frozen range to a higher offset. Slicing\n\t\t// req[\"messages\"][:frozenLen] AFTER inject would capture the injected\n\t\t// turns at the head and corrupt the stored frozen prefix, breaking\n\t\t// cache continuity for 5-9 turns after every emergency-sawtooth.\n\t\tif frozenLen := s.frozenStubs.LengthFor(threadID); frozenLen > 0 {\n\t\t\tif msgs, ok := req[\"messages\"].([]any); ok && len(msgs) >= frozenLen {\n\t\t\t\tif s.frozenStubs.UpdateMessages(threadID, msgs[:frozenLen]) {\n\t\t\t\t\ts.logger.Printf(\"[req %d %s tid=%s] FROZEN-SNAPSHOT: refreshed %d pre-inject messages\", reqIdx, proj, threadID, frozenLen)\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\t// Briefing injection: prepend user/assistant turn pair at beginning of messages.\n\t\t// Static per session → stable prefix → cacheable. Must be before dialog injection.\n\t\tif s.injectBriefingTurn(req, reqIdx, proj, threadID) {\n\t\t\tneedsReserialization = true\n\t\t}\n\n\t\t// Code Map injection: insert after briefing turn pair.\n\t\tif s.injectCodeMapTurn(req, reqIdx, proj, threadID) {\n\t\t\tneedsReserialization = true\n\t\t}\n\n\t\t// Active capabilities injection: insert user/assistant pair directly\n\t\t// before the last user message. Fresh per turn (no cache) so\n\t\t// activate/deactivate takes effect immediately. Must run AFTER\n\t\t// briefing/codeMap (both prepend at beginning) and BEFORE dialog\n\t\t// injection (which appends at end) to preserve cache for the\n\t\t// briefing prefix and keep the injection close to the real user\n\t\t// turn.\n\t\tif s.injectCapabilitiesTurn(req, threadID) {\n\t\t\tneedsReserialization = true\n\t\t}\n\n\t\t// Dialog injection: append at end of messages with pseudo-padding for alternation.\n\t\t// Always works regardless of tool_result chains or conversation state.\n\t\tdr := s.checkDialogMessages(threadID, proj)\n\t\tif dr.Extra != \"\" {\n\t\t\t// Prepend session ID for agent-to-agent communication\n\t\t\tdr.Extra = fmt.Sprintf(\"DEINE_SESSION_ID: %s\\n\", threadID) + dr.Extra\n\t\t\tinjected := false\n\t\t\tif msgs, ok := req[\"messages\"].([]any); ok && len(msgs) > 0 {\n\t\t\t\t// Pad with empty assistant if last message is user (maintains alternation)\n\t\t\t\tlastMsg, _ := msgs[len(msgs)-1].(map[string]any)\n\t\t\t\tif lastMsg != nil && lastMsg[\"role\"] == \"user\" {\n\t\t\t\t\tmsgs = append(msgs, map[string]any{\n\t\t\t\t\t\t\"role\": \"assistant\",\n\t\t\t\t\t\t\"content\": \"​\", // zero-width space — invisible padding\n\t\t\t\t\t})\n\t\t\t\t}\n\t\t\t\t// Append dialog as new user message (becomes the last message Claude sees)\n\t\t\t\tmsgs = append(msgs, map[string]any{\n\t\t\t\t\t\"role\": \"user\",\n\t\t\t\t\t\"content\": \"\\n\" + dr.Extra + \"\\n\",\n\t\t\t\t})\n\t\t\t\treq[\"messages\"] = msgs\n\t\t\t\tneedsReserialization = true\n\t\t\t\tinjected = true\n\t\t\t}\n\t\t\t// Mark as read after 3 injection turns — long enough for Claude to process,\n\t\t\t// resilient against fast ping-pong sessions (time-based was broken for <30s turns).\n\t\t\tif injected && dr.HasUnread && dr.SessionID != \"\" {\n\t\t\t\tif s.shouldMarkChannelRead(dr) {\n\t\t\t\t\ts.markDialogRead(dr)\n\t\t\t\t}\n\t\t\t}\n\t\t\ts.logger.Printf(\"[dialog] injection injected=%v extra_len=%d\", injected, len(dr.Extra))\n\t\t}\n\t}\n\n\t// Signal tools are NOT injected into the main request.\n\t// They are collected via a separate async reflection call after the response.\n\t// See fireReflectionCall() in reflection.go.\n\t// NEVER put _signal_* tools back here — the model ignores them among 44+ real tools.\n\n\t// Legacy assistant annotation: timestamp on last assistant message after cache breakpoint.\n\t// User timestamps are now handled by TimestampStore (injected earlier in the pipeline).\n\tif threadID != \"\" {\n\t\tif msgs, ok := req[\"messages\"].([]any); ok && len(msgs) > 0 {\n\t\t\t// Find last cache_control breakpoint in messages\n\t\t\tcacheIdx := -1\n\t\t\tfor i, m := range msgs {\n\t\t\t\tmsg, _ := m.(map[string]any)\n\t\t\t\tif msg == nil {\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\t\t\t\tif _, has := msg[\"cache_control\"]; has {\n\t\t\t\t\tcacheIdx = i\n\t\t\t\t}\n\t\t\t\tif content, ok := msg[\"content\"].([]any); ok {\n\t\t\t\t\tfor _, block := range content {\n\t\t\t\t\t\tif b, ok := block.(map[string]any); ok {\n\t\t\t\t\t\t\tif _, has := b[\"cache_control\"]; has {\n\t\t\t\t\t\t\t\tcacheIdx = i\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t\tstartIdx := cacheIdx + 1\n\t\t\tif startIdx < 0 {\n\t\t\t\tstartIdx = 0\n\t\t\t}\n\t\t\ts.responseTsMu.RLock()\n\t\t\trespTime, hasRespTime := s.responseTimes[threadID]\n\t\t\ts.responseTsMu.RUnlock()\n\t\t\tif hasRespTime {\n\t\t\t\tfor i := len(msgs) - 1; i >= startIdx; i-- {\n\t\t\t\t\tmsg, _ := msgs[i].(map[string]any)\n\t\t\t\t\tif msg != nil && msg[\"role\"] == \"assistant\" {\n\t\t\t\t\t\tmeta := fmt.Sprintf(\"[%s] [msg:%d]\", respTime.Format(\"2006-01-02 15:04:05\"), i)\n\t\t\t\t\t\tprependMeta(msg, meta)\n\t\t\t\t\t\tneedsReserialization = true\n\t\t\t\t\t\tbreak\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\t// Self-Priming: DISABLED (experimental, see signal_handlers.go comment block).\n\t// The reflection-based selfprime (Haiku) was too inaccurate to be useful —\n\t// the main model adapts to user context organically from message history.\n\t// Kept for reference; re-enable by uncommenting below + setting ShouldActivate=true.\n\t//\n\t// s.selfPrimeMu.RLock()\n\t// anchor := s.selfPrimes[threadID]\n\t// s.selfPrimeMu.RUnlock()\n\t// if anchor != \"\" {\n\t// \tif msgs, ok := req[\"messages\"].([]any); ok && len(msgs) > 0 {\n\t// \t\tlastMsg, _ := msgs[len(msgs)-1].(map[string]any)\n\t// \t\tif lastMsg != nil && lastMsg[\"role\"] == \"user\" {\n\t// \t\t\tprimeBlock := map[string]any{\n\t// \t\t\t\t\"type\": \"text\",\n\t// \t\t\t\t\"text\": \"[self-prime]\\n[Self-Prime von deiner letzten Antwort]: \" + anchor,\n\t// \t\t\t}\n\t// \t\t\tif content, ok := lastMsg[\"content\"].([]any); ok {\n\t// \t\t\t\tlastMsg[\"content\"] = append(content, primeBlock)\n\t// \t\t\t} else if text, ok := lastMsg[\"content\"].(string); ok {\n\t// \t\t\t\tlastMsg[\"content\"] = []any{\n\t// \t\t\t\t\tmap[string]any{\"type\": \"text\", \"text\": text},\n\t// \t\t\t\t\tprimeBlock,\n\t// \t\t\t\t}\n\t// \t\t\t}\n\t// \t\t\tneedsReserialization = true\n\t// \t\t}\n\t// \t}\n\t// }\n\n\t// Prompt Caching: inject cache_control breakpoints on system + tools\n\t// Sawtooth: disabled (all 4 slots are used by CC system (2) + briefing (1) + CC messages (1))\n\t// Legacy: count existing breakpoints and fill up remaining slots.\n\tif !s.cfg.SawtoothEnabled && s.cacheGate.ShouldCache() {\n\t\tif n := InjectCacheBreakpoints(req, s.logger); n > 0 {\n\t\t\tneedsReserialization = true\n\t\t\ts.logger.Printf(\"[req %d %s] prompt cache: %d breakpoints injected\", reqIdx, proj, n)\n\t\t} else {\n\t\t\ts.logger.Printf(\"[req %d %s] prompt cache: no budget (4/4 already set)\", reqIdx, proj)\n\t\t}\n\t}\n\n\t// Final: upgrade cache TTL + enforce breakpoint limit (must run AFTER all injections + InjectCacheBreakpoints)\n\t// Shift the last messages-breakpoint from unstable user message to stable assistant message.\n\t// Text user messages change between turns (timestamps, reminders, structure); assistant messages don't.\n\t// Tool-result messages are excluded — they are stable cache anchors.\n\tif ShiftMessageBreakpoint(req) {\n\t\tneedsReserialization = true\n\t}\n\t// Normalize cache_control TTL across all blocks to prevent Anthropic from\n\t// rejecting requests with increasing TTLs in block order. Auto-detects 1h\n\t// when any existing block already carries it (config, Claude Code\n\t// passthrough on --resume, or stale frozen prefix from an earlier turn).\n\tif n := NormalizeCacheTTL(req, s.cfg.CacheTTL); n > 0 {\n\t\tneedsReserialization = true\n\t\ts.logger.Printf(\"[req %d %s] cache TTL normalized: %d blocks\", reqIdx, proj, n)\n\t}\n\tif n := EnforceCacheBreakpointLimit(req, maxCacheBreakpoints); n > 0 {\n\t\tneedsReserialization = true\n\t\ts.logger.Printf(\"[req %d %s] prompt cache: trimmed %d surplus breakpoints\", reqIdx, proj, n)\n\t}\n\n\t// Pre-flight: validate tool_use/tool_result pairing before forwarding.\n\t// Repairs orphans created by Stubify/Collapse/Injection pipeline.\n\tif msgs, ok := req[\"messages\"].([]any); ok {\n\t\trepaired, orphans := validateToolPairs(msgs, s.logger)\n\t\tif orphans > 0 {\n\t\t\treq[\"messages\"] = repaired\n\t\t\ts.logger.Printf(\"%s[req %d %s tid=%s] validate: repaired %d orphan tool_result(s)%s\", colorOrange, reqIdx, proj, threadID, orphans, colorReset)\n\t\t\tneedsReserialization = true\n\t\t}\n\t}\n\n\t// Early-out: skip re-serialization when nothing changed\n\tif !needsReserialization {\n\t\ts.logger.Printf(\"%s[req %d %s tid=%s] %d messages, ~%dk tokens (no stubbing, threshold=%dk)%s\", colorDim, reqIdx, proj, threadID, len(messages), totalTokens/1000, s.effectiveTokenThreshold(model)/1000, colorReset)\n\t\ts.forwardWithAnnotation(w, r, body, reqIdx, toolUseIDs, proj, threadID, msgCount, totalTokens)\n\t\treturn\n\t}\n\n\t// Re-serialize\n\tbody, err = json.Marshal(req)\n\tif err != nil {\n\t\ts.logger.Printf(\"%s[req %d %s tid=%s] re-marshal error: %v%s\", colorRed, reqIdx, proj, threadID, err, colorReset)\n\t\thttp.Error(w, \"internal proxy error\", http.StatusInternalServerError)\n\t\treturn\n\t}\n\n\t// Debug: dump final request body to file (enable with YESMEM_PROXY_DEBUG=1)\n\tif needsReserialization && os.Getenv(\"YESMEM_PROXY_DEBUG\") == \"1\" {\n\t\tdumpDir := filepath.Join(s.cfg.DataDir, \"debug\")\n\t\tos.MkdirAll(dumpDir, 0755)\n\t\tts := time.Now().Format(\"20060102-150405\")\n\t\tdumpPath := fmt.Sprintf(\"%s/req_%s_%d.json\", dumpDir, ts, reqIdx)\n\t\tos.WriteFile(dumpPath, body, 0644)\n\t\ts.logger.Printf(\"[req %d] dumped %dk to %s\", reqIdx, len(body)/1024, dumpPath)\n\t}\n\n\t// Task #7: Mark this request fingerprint for retry detection\n\tif !isRetryReq {\n\t\ts.markRequest(fp)\n\t}\n\n\t// Forward to Anthropic with SSE annotation extraction\n\t// Use original msgCount (pre-injection) so actual-based estimation stays consistent across turns\n\t// Use actual-based totalTokens as estimate when available (more accurate than full recount)\n\tfinalEstimate := totalTokens\n\tif s.sawtoothTrigger == nil || s.sawtoothTrigger.GetLastTokens(threadID) == 0 {\n\t\t// First request: no actual yet, do a full recount of post-injection messages\n\t\tfinalMsgs, _ := req[\"messages\"].([]any)\n\t\tfinalEstimate = s.measureOverhead(req) + s.countMessageTokens(finalMsgs)\n\t}\n\ts.logger.Printf(\"[req %d %s tid=%s] FINAL: ~%dk estimated (after all injections)\", reqIdx, proj, threadID, finalEstimate/1000)\n\tif raw := s.getRawEstimate(threadID); raw > 0 {\n\t\ts.cacheStatusWriter.UpdateRawForThread(threadID, raw)\n\t}\n\ts.forwardWithAnnotation(w, r, body, reqIdx, toolUseIDs, proj, threadID, msgCount, finalEstimate)\n}\n\n// getPromptFlags resolves the effective prompt flags for a profile, falling back\n// to the Server's legacy flat config if promptCfg is nil (backward compatibility).\nfunc (s *Server) getPromptFlags(profile models.PromptProfile) *config.PromptFlags {\n\tif s.promptCfg != nil {\n\t\treturn s.promptCfg.EffectivePromptFlags(profile)\n\t}\n\t// Legacy path: use flat fields as Claude defaults.\n\treturn &config.PromptFlags{\n\t\tUngate: s.cfg.PromptUngate,\n\t\tRewrite: s.cfg.PromptRewrite,\n\t\tEnhance: s.cfg.PromptEnhance,\n\t\tToolPrefs: s.cfg.PromptToolPrefs,\n\t\tOutputDiscipline: s.cfg.PromptOutputDiscipline,\n\t\tCodingDiscipline: s.cfg.PromptCodingDiscipline,\n\t\tBeweislast: s.cfg.PromptBeweislast,\n\t\tFable: s.cfg.PromptFable,\n\t\tScopeDiscipline: s.cfg.PromptScopeDiscipline,\n\t\tDelegationContract: s.cfg.PromptDelegationContract,\n\t\tClarifyFirst: s.cfg.PromptClarifyFirst,\n\t\tCodeToolsFirst: s.cfg.PromptCodeToolsFirst,\n\t\tWikiFirst: s.cfg.PromptWikiFirst,\n\t\tPatternSuggest: s.cfg.PromptPatternSuggest,\n\t}\n}\n\n// getThreadCWD returns the cached working directory for a thread.\nfunc (s *Server) getThreadCWD(threadID string) string {\n\ts.threadCWDMu.RLock()\n\tdefer s.threadCWDMu.RUnlock()\n\tif s.threadCWD == nil {\n\t\treturn \"\"\n\t}\n\treturn s.threadCWD[threadID]\n}\n\n// setThreadCWD caches the working directory for a thread.\nfunc (s *Server) setThreadCWD(threadID, cwd string) {\n\ts.threadCWDMu.Lock()\n\tdefer s.threadCWDMu.Unlock()\n\tif s.threadCWD == nil {\n\t\ts.threadCWD = make(map[string]string)\n\t}\n\ts.threadCWD[threadID] = cwd\n}\n\n// resolveOpenAITarget returns the upstream URL for an OpenAI-format request.\n// Checks ProviderTargets by prefix match (e.g. \"deepseek\" matches \"deepseek-v4-pro\"),\n// then by exact model name, then falls back to OpenAITargetURL.\nfunc (s *Server) resolveOpenAITarget(model string) string {\n\tmodelLower := strings.ToLower(model)\n\tif len(s.cfg.ProviderTargets) > 0 {\n\t\tfor key, url := range s.cfg.ProviderTargets {\n\t\t\tif url == \"\" {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tkeyLower := strings.ToLower(key)\n\t\t\tif strings.HasPrefix(modelLower, keyLower) || strings.HasPrefix(modelLower, keyLower+\"-\") {\n\t\t\t\treturn strings.TrimRight(url, \"/\")\n\t\t\t}\n\t\t}\n\t\tfor key, url := range s.cfg.ProviderTargets {\n\t\t\tif url != \"\" && strings.EqualFold(key, model) {\n\t\t\t\treturn strings.TrimRight(url, \"/\")\n\t\t\t}\n\t\t}\n\t}\n\t// Auto-discovered targets (secondary — explicit provider_targets takes priority)\n\tif len(s.autoProviderTargets) > 0 {\n\t\tif url, ok := s.autoProviderTargets[modelLower]; ok && url != \"\" {\n\t\t\treturn url\n\t\t}\n\t}\n\n\tif s.cfg.OpenAITargetURL != \"\" {\n\t\treturn s.cfg.OpenAITargetURL\n\t}\n\treturn s.cfg.TargetURL\n}\n\n// resolveAnthropicTarget returns the upstream URL for an Anthropic-format request.\n// Checks ProviderTargets by prefix match (e.g. \"deepseek\" matches \"deepseek-v4-pro\"),\n// then by exact model name, then falls back to TargetURL.\nfunc (s *Server) resolveAnthropicTarget(model string) string {\n\tmodelLower := strings.ToLower(model)\n\tif len(s.cfg.ProviderTargets) > 0 {\n\t\tfor key, url := range s.cfg.ProviderTargets {\n\t\t\tif url == \"\" {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tkeyLower := strings.ToLower(key)\n\t\t\tif strings.HasPrefix(modelLower, keyLower) || strings.HasPrefix(modelLower, keyLower+\"-\") {\n\t\t\t\treturn strings.TrimRight(url, \"/\")\n\t\t\t}\n\t\t}\n\t\tfor key, url := range s.cfg.ProviderTargets {\n\t\t\tif url != \"\" && strings.EqualFold(key, model) {\n\t\t\t\treturn strings.TrimRight(url, \"/\")\n\t\t\t}\n\t\t}\n\t}\n\treturn s.cfg.TargetURL\n}\n\n// bytesPerToken is the approximation factor for token estimation.\n// Anthropic recommends ~3.5 chars/token; we use 3.6 for slight overestimation\n// (triggers stubbing a bit earlier = safer for context budget).\nconst bytesPerToken = 3.6\n\n// TokenEstimateFunc is the central token estimation function.\n// All stubbing/budget decisions use this. Receives extracted text content\n// (not JSON wire format) so the tokenizer counts actual content tokens.\ntype TokenEstimateFunc func(text string) int\n"}, "bc3df4e7a6360e65e9fee54d2e26db07bd756faa": {"t": 1785759343.7289464, "n": 77015, "h": "f968e4006dec9dc6", "c": "package proxy\n\nimport (\n\t\"context\"\n\t\"crypto/tls\"\n\t\"encoding/json\"\n\t\"fmt\"\n\t\"io\"\n\t\"log\"\n\t\"net\"\n\t\"net/http\"\n\t\"os\"\n\t\"os/signal\"\n\t\"path/filepath\"\n\t\"strconv\"\n\t\"strings\"\n\t\"sync\"\n\t\"sync/atomic\"\n\t\"syscall\"\n\t\"time\"\n\n\t\"github.com/carsteneu/yesmem/internal/buildinfo\"\n\t\"github.com/carsteneu/yesmem/internal/config\"\n\t\"github.com/carsteneu/yesmem/internal/models\"\n\ttokenizer \"github.com/qhenkart/anthropic-tokenizer-go\"\n)\n\n// Config holds proxy configuration.\ntype Config struct {\n\tListenAddr string // e.g. \":9099\"\n\tTargetURL string // e.g. \"https://api.anthropic.com\"\n\tTokenThreshold int // trigger stubbing above this estimated token count\n\tTokenMinimumThreshold int // stub down to this floor\n\tTokenThresholds map[string]int // model-specific thresholds: {\"opus\": 180000, \"haiku\": 130000}\n\tKeepRecent int // number of messages to always keep unmodified\n\tDataDir string // yesmem data directory for DB access\n\tOpenAITargetURL string // upstream for OpenAI-format clients; if empty, uses TargetURL\n\tProviderTargets map[string]string // per-provider upstream URLs, e.g. {\"deepseek\": \"https://api.deepseek.com\"}\n\tAutoConfigureProviders bool // enable auto-discovery of provider targets from opencode config\n\n\t// Signal reflection\n\tSignalsEnabled bool // enable async signal reflection calls\n\tSignalsMode string // \"reflection\" = separate API call\n\tSignalsEveryNTurns int // every N end_turn responses\n\tSignalsModel string // full model ID, e.g. \"claude-sonnet-4-6\"\n\tAPIKey string // needed for reflection API calls\n\n\t// Sawtooth cache optimization\n\tSawtoothEnabled bool // use sawtooth instead of progressive decay (default: true)\n\tCacheTTL string // \"ephemeral\" (5m) or \"1h\" (extended)\n\n\t// Usage deflation: scale down input_tokens reported to CC to suppress \"Context low\" warning.\n\t// 0 = disabled, 0.7 = report 70% of actual tokens. Real values kept for internal tracking.\n\tUsageDeflationFactor float64\n\n\t// System prompt rewriting\n\tPromptUngate bool // strip \"may or may not be relevant\" disclaimer from CLAUDE.md injection\n\tPromptRewrite bool // strip \"Output efficiency\" + \"short and concise\", inject Ant-quality directives\n\tPromptEnhance bool // CLAUDE.md authority reinforcement, comment discipline, persona-based tone\n\tPromptToolPrefs bool // inject [yesmem-tool-prefs] Edit/Write preference + error-semantics warning\n\tPromptOutputDiscipline bool // inject [yesmem-output-discipline] no-preamble + no-skill-eval + exploratory-heuristic\n\tPromptCodingDiscipline bool // inject [yesmem-coding-discipline] read-before-propose + no-brute-force + no-half-finished\n\tPromptBeweislast bool // inject [yesmem-beweislast] fabrication-guard + claim-vs-proof + stance-under-challenge + tool-result-honesty + long-context-erosion\n\tPromptFable bool // inject [yesmem-fable] baseline-comparison + rollback-path + readback-check for high-stakes changes\n\tPromptScopeDiscipline bool // inject [yesmem-scope-discipline] deliver-A-not-A+B+C + adjacent-findings-separate + scope-bound-authorization\n\tPromptDelegationContract bool // inject [yesmem-delegation-contract] self-contained-prompts + parallel-dispatch\n\tPromptClarifyFirst bool // inject [yesmem-clarify-first] clarify only when alternative interpretations produce materially different work\n\tPromptCodeToolsFirst bool // inject [yesmem-code-tools-first] prefer MCP code-navigation tools over Agent spawns\n\tPromptWikiFirst bool // inject [yesmem-wiki-first] check per-file wiki BEFORE editing\n\tPromptPatternSuggest bool // record repeated shell-command shapes for offline cap-suggestion analysis\n\tEffortFloor string // minimum effort level: \"low\", \"medium\", \"high\", \"max\" (empty = off)\n\tSkillEvalInject string // \"true\" = verbose eval, \"silent\" = internal eval only, \"false\" = disabled\n\n\t// Cache keepalive\n\tCacheKeepaliveEnabled bool\n\tCacheKeepaliveMode string // \"auto\", \"5m\", \"1h\"\n\tCacheKeepalivePings5m int\n\tCacheKeepalivePings1h int\n\tCacheKeepaliveMinMessages int // skip keepalive when request body has fewer messages (0 = always)\n\n\t// Forked agents\n\tForkedAgentsEnabled bool\n\tForkedAgentsModel string // full model ID\n\tForkedAgentsTokenGrowthTrigger int\n\tForkedAgentsMaxFailures int\n\tForkedAgentsMaxForksPerSession int\n\tForkedAgentsDebug bool\n\n\t// Quality model fallback for forked agents\n\tQualityModelID string\n\n\t// Cache state management\n\tResetCache bool // clear persisted frozen stubs and decay state on startup\n\n\t// ModelFeatures: per-model behavioral feature gates (prefix-matched).\n\t// Falls back to FeatureDefaults for models not listed.\n\tModelFeatures map[string]*config.FeatureGates `yaml:\"model_features\"`\n\tFeatureDefaults *config.FeatureGates `yaml:\"feature_defaults\"`\n\n\t// Custom system prompt: shared SYSTEM.md template for OpenCode and Claude Code pipelines.\n\tCustomSystemPrompt CustomSystemPromptConfig\n}\n\n// CustomSystemPromptConfig controls which proxy pipelines use the SYSTEM.md template.\ntype CustomSystemPromptConfig struct {\n\tEnabledOpenCode bool\n\tEnabledClaudeCode bool\n\tEnabledCodex bool\n\tTemplatePath string\n\tModelTemplates map[string]string // per-model overrides: modelPattern → templatePath\n}\n\nconst maxAnnotations = 5000 // evict oldest when exceeded\n\n// ANSI color codes for log output\nconst (\n\tcolorReset = \"\\033[0m\"\n\tcolorDim = \"\\033[2m\" // gray — passthrough / no stubbing\n\tcolorGreen = \"\\033[32m\" // stubbed / compressed\n\tcolorLightGreen = \"\\033[92m\" // FINAL without compression\n\tcolorBlue = \"\\033[34m\" // injection\n\tcolorOrange = \"\\033[33m\" // skip / retry\n\tcolorYellow = \"\\033[33m\" // warnings\n\tcolorRed = \"\\033[31m\" // errors\n)\n\n// Server is the infinite-thread proxy.\ntype Server struct {\n\tcfg Config\n\tautoProviderTargets map[string]string // auto-discovered modelID → upstreamURL\n\thttpClient *http.Client\n\tlogger *log.Logger\n\tversion string // build version for log tracing\n\trequestIdx atomic.Int64\n\n\t// Post-hoc annotations: tool_use_id → first ~120 chars of Claude's response\n\tmu sync.RWMutex\n\tannotations map[string]string\n\n\t// Progressive decay tracker\n\tdecay *DecayTracker\n\n\t// Living narrative block\n\tnarrative *Narrative\n\n\t// Pivot moments cache (from daemon)\n\tpivotMu sync.RWMutex\n\tpivotTexts []string\n\tpivotCached time.Time\n\n\t// Aggregate stats (Task #2)\n\tstats *ProxyStats\n\n\t// Local tokenizer for accurate token counting\n\ttokenizer *tokenizer.Tokenizer\n\n\t// Cached overhead tokens (system + tools), set on first request per thread\n\toverheadMu sync.RWMutex\n\toverheadTokens int // unused, kept for interface compat\n\n\t// Hysteresis state for stubbing (Task #6)\n\tstubActive atomic.Bool\n\n\t// Daemon connection state — first successful connect flips to true\n\tdaemonReady atomic.Bool\n\n\t// Retry detection (Task #7)\n\tretryMu sync.RWMutex\n\tlastFingerprint string\n\n\t// Session start time for temporal annotation in archive blocks\n\tsessionStartMu sync.RWMutex\n\tsessionStartTime time.Time\n\n\t// Runtime config override (set via MCP set_config tool)\n\tconfigOverrideMu sync.RWMutex\n\ttokenThresholdOverrides map[string]int // model-key → threshold, \"\" = global fallback\n\n\t// Prompt rewrite miss logging: function+Claude Code version → last log time\n\trewriteMissMu sync.Mutex\n\trewriteMissLog map[string]time.Time\n\n\t// Briefing cache — keyed by threadID. Each Claude Code session thread gets\n\t// its own briefing+codemap snapshot so a sawtooth refreeze on thread A\n\t// cannot invalidate thread B's cached message-prefix hash. The cache entry\n\t// remembers which project it was loaded for; switching project for the\n\t// same thread evicts the old entry on next read.\n\tbriefingMu sync.RWMutex\n\tbriefingCache map[string]briefingEntry\n\n\t// briefingLoader is an optional test-only seam for refreshBriefing.\n\t// Nil in production → refreshBriefing falls back to s.loadBriefing.\n\tbriefingLoader func(project, projectDir string) briefingData\n\n\t// Cognitive signal bus — routes _signal_* tool calls to handlers\n\tsignalBus *SignalBus\n\n\t// Inline reflection: track which learning IDs were injected per thread\n\tlastInjectedIDsMu sync.Mutex\n\tlastInjectedIDs map[string]map[int64]string // threadID → id → source (\"briefing\"|\"associative\"|\"fresh\")\n\tsessionInjectCounts map[string]map[int64]int // threadID → learningID → injection count this session\n\tlastTurnInjected map[string]map[int64]bool // threadID → learningIDs injected in previous turn\n\n\t// Self-priming anchors: threadID → last self-prime text\n\tselfPrimeMu sync.RWMutex\n\tselfPrimes map[string]string\n\n\t// Timestamp tracking: threadID → time of last response completion\n\tresponseTsMu sync.RWMutex\n\tresponseTimes map[string]time.Time\n\n\t// Think reminder: per-thread request counter (replaces hook-think file-based counter)\n\tthinkMu sync.Mutex\n\tthinkCounters map[string]int // threadID → request count\n\n\tchannelMu sync.Mutex\n\tchannelInjectCount map[string]int // sessionID → injection turn count\n\n\t// Prompt cache gating — enables cache_control breakpoints when requests are frequent\n\tcacheGate *CacheGate\n\n\t// Sawtooth cache optimization\n\tfrozenStubs *FrozenStubs\n\teagerStubMemory *EagerStubMemory\n\tcapsCache *CapsCache\n\tsawtoothTrigger *SawtoothTrigger\n\ttimestampStore *TimestampStore\n\tcacheStatusWriter *CacheStatusWriter\n\tcacheKeepalive *CacheKeepalive\n\tcacheTTLDetector *CacheTTLDetector\n\t// promptCfg is the profile-aware prompt configuration from config.ProxyConfig.\n\t// Used to resolve EffectivePromptFlags(profile) for multi-agent prompt isolation.\n\tpromptCfg *config.ProxyConfig\n\t// threadCWD caches the working directory per thread (for opencode: only in first message)\n\tthreadCWDMu sync.RWMutex\n\tthreadCWD map[string]string // threadID → cwd\n\n\t// Cumulative token savings from collapsing (per threadID)\n\trawSavingsMu sync.Mutex\n\trawSavings map[string]int\n\n\t// Skill hint tracking: per-thread active skills\n\tskillTracker *skillHintTracker\n\n\t// Forked agent state: per-thread token growth + failure tracking\n\tforkState *ForkState\n\tforkConfigs []ForkConfig\n\n\t// Custom system prompt template for OpenCode (loaded from DataDir/SYSTEM.md)\n\tcustomSystemPrompt []byte\n\n\t// Per-model template overrides (loaded from config CustomSystemPrompt.ModelTemplates)\n\tmodelTemplates map[string][]byte\n\n\t// Rules re-injection: condensed CLAUDE.md rules injected every ~40k tokens\n\trulesMu sync.RWMutex\n\trulesBlock string // cached condensed rules (fetched once from daemon)\n\trulesTokenCount map[string]int // threadID → tokens since last rules injection\n\trulesCollapseInjected map[string]bool // threadID → true if collapse already injected rules (reset by normal inject)\n\tmsgCounters *msgCounters // global per-thread msg counter, persists across collapses\n\n\t// Loop detection: per-thread warning state\n\tloopMu sync.Mutex\n\tloopStates map[string]*LoopState // threadID → state\n\n\t// Injection overhead: per-thread delta between API-actual and local BPE estimate\n\tinjectionOverheadMu sync.RWMutex\n\tinjectionOverhead map[string]int // threadID → overhead tokens\n}\n\n// Run starts the proxy server and blocks until interrupted.\nfunc Run(cfg Config) error {\n\ts := &Server{\n\t\tcfg: cfg,\n\t\thttpClient: &http.Client{\n\t\t\tTimeout: 10 * time.Minute,\n\t\t\tTransport: &http.Transport{\n\t\t\t\tTLSClientConfig: &tls.Config{},\n\t\t\t\tMaxIdleConns: 10,\n\t\t\t\tIdleConnTimeout: 90 * time.Second,\n\t\t\t\tDisableCompression: true,\n\t\t\t\tResponseHeaderTimeout: 60 * time.Second,\n\t\t\t},\n\t\t},\n\t\tlogger: createLogger(cfg.DataDir),\n\t\tannotations: make(map[string]string),\n\t\tdecay: NewDecayTracker(),\n\t\tnarrative: NewNarrative(),\n\t\tstats: &ProxyStats{startTime: time.Now()},\n\t\tselfPrimes: make(map[string]string),\n\t\tlastInjectedIDs: make(map[string]map[int64]string),\n\t\tversion: buildinfo.Version,\n\t\tsessionInjectCounts: make(map[string]map[int64]int),\n\t\tlastTurnInjected: make(map[string]map[int64]bool),\n\t\tresponseTimes: make(map[string]time.Time),\n\t\tthinkCounters: make(map[string]int),\n\t\tchannelInjectCount: make(map[string]int),\n\t\trewriteMissLog: make(map[string]time.Time),\n\t\tcacheGate: NewCacheGate(cacheGapForTTL(cfg.CacheTTL)),\n\t\tfrozenStubs: NewFrozenStubsWithTTL(sawtoothTTLForCacheTTL(cfg.CacheTTL)),\n\t\teagerStubMemory: NewEagerStubMemory(),\n\t\tcapsCache: NewCapsCache(),\n\t\ttimestampStore: NewTimestampStore(),\n\t\tsawtoothTrigger: NewSawtoothTrigger(cacheGapForTTL(cfg.CacheTTL), cfg.TokenThreshold, cfg.TokenMinimumThreshold),\n\t\tcacheStatusWriter: NewCacheStatusWriter(cfg.DataDir, cfg.CacheTTL, cfg.TokenThreshold, cfg.TokenMinimumThreshold),\n\t\tcacheTTLDetector: NewCacheTTLDetectorWithPersist(cfg.DataDir),\n\t\tskillTracker: newSkillHintTracker(),\n\t\trulesTokenCount: make(map[string]int),\n\t\trulesCollapseInjected: make(map[string]bool),\n\t\tmsgCounters: newMsgCounters(),\n\t\tloopStates: make(map[string]*LoopState),\n\t\tinjectionOverhead: make(map[string]int),\n\t\tforkState: NewForkState(cfg.ForkedAgentsTokenGrowthTrigger, 60000, cfg.ForkedAgentsMaxFailures, cfg.ForkedAgentsMaxForksPerSession),\n\t\tforkConfigs: []ForkConfig{},\n\t}\n\n\t// Load custom system prompt from configured path\n\tif cfg.CustomSystemPrompt.TemplatePath != \"\" &&\n\t\t(cfg.CustomSystemPrompt.EnabledOpenCode || cfg.CustomSystemPrompt.EnabledClaudeCode || cfg.CustomSystemPrompt.EnabledCodex) {\n\t\ts.customSystemPrompt = loadSystemPromptFromPath(cfg.CustomSystemPrompt.TemplatePath)\n\t\tif s.customSystemPrompt != nil {\n\t\t\ts.logger.Printf(\"[proxy] loaded custom system prompt from %s (%d bytes, opencode=%v cc=%v codex=%v)\",\n\t\t\t\tcfg.CustomSystemPrompt.TemplatePath, len(s.customSystemPrompt),\n\t\t\t\tcfg.CustomSystemPrompt.EnabledOpenCode, cfg.CustomSystemPrompt.EnabledClaudeCode, cfg.CustomSystemPrompt.EnabledCodex)\n\t\t} else {\n\t\t\ts.logger.Printf(\"[proxy] WARNING: custom_system_prompt enabled but template not found at %q — feature disabled\",\n\t\t\t\tcfg.CustomSystemPrompt.TemplatePath)\n\t\t}\n\t}\n\n\t// Load per-model template overrides\n\tif len(cfg.CustomSystemPrompt.ModelTemplates) > 0 {\n\t\ts.modelTemplates = make(map[string][]byte, len(cfg.CustomSystemPrompt.ModelTemplates))\n\t\tfor pattern, path := range cfg.CustomSystemPrompt.ModelTemplates {\n\t\t\ttpl := loadSystemPromptFromPath(path)\n\t\t\tif tpl != nil {\n\t\t\t\ts.modelTemplates[pattern] = tpl\n\t\t\t\ts.logger.Printf(\"[proxy] loaded model template %q from %s (%d bytes)\", pattern, path, len(tpl))\n\t\t\t} else {\n\t\t\t\ts.logger.Printf(\"[proxy] WARNING: model template %q not found at %q — skipping\", pattern, path)\n\t\t\t}\n\t\t}\n\t}\n\n\t// Log persisted detection state\n\tif sup := s.cacheTTLDetector.Is1hSupported(); sup != nil {\n\t\ts.logger.Printf(\"[proxy] loaded persisted TTL detection: 1h_supported=%v\", *sup)\n\t\tif *sup && s.frozenStubs != nil {\n\t\t\ts.frozenStubs.UpdateTTL(sawtoothTTLForCacheTTL(\"1h\"))\n\t\t}\n\t}\n\n\tif cfg.CacheKeepaliveEnabled {\n\t\ts.cacheKeepalive = NewCacheKeepalive(CacheKeepaliveConfig{\n\t\t\tTarget: cfg.TargetURL,\n\t\t\tProviderTargets: cfg.ProviderTargets,\n\t\t\tMode: cfg.CacheKeepaliveMode,\n\t\t\tPings5m: cfg.CacheKeepalivePings5m,\n\t\t\tPings1h: cfg.CacheKeepalivePings1h,\n\t\t\tMinMessages: cfg.CacheKeepaliveMinMessages,\n\t\t\tDetector: s.cacheTTLDetector,\n\t\t\tLogger: s.logger,\n\t\t\tOnPing: func(threadID string, cacheRead, cacheWrite int) {\n\t\t\t\ts.cacheTTLDetector.RecordResponse(cacheRead, cacheWrite, 0)\n\t\t\t\tif s.frozenStubs != nil {\n\t\t\t\t\ts.frozenStubs.Touch(threadID)\n\t\t\t\t}\n\t\t\t\tif s.sawtoothTrigger != nil {\n\t\t\t\t\ts.sawtoothTrigger.TouchRequestTime(threadID)\n\t\t\t\t}\n\t\t\t},\n\t\t})\n\t}\n\n\t// Wire detector + keepalive to status writer for terminal display\n\ts.cacheStatusWriter.SetDetector(s.cacheTTLDetector)\n\tif s.cacheKeepalive != nil {\n\t\ts.cacheStatusWriter.SetKeepalive(s.cacheKeepalive)\n\t}\n\n\t// Register forked agent configs\n\tif cfg.ForkedAgentsEnabled {\n\t\tmodel := cfg.ForkedAgentsModel\n\t\ts.forkConfigs = append(s.forkConfigs, NewExtractAndEvaluateConfig(model))\n\t\tmodelDesc := model\n\t\tif modelDesc == \"\" {\n\t\t\tmodelDesc = \"(same as main thread)\"\n\t\t}\n\t\ts.logger.Printf(\"[proxy] forked agents enabled: %d configs, model=%s, trigger=%dk tokens\",\n\t\t\tlen(s.forkConfigs), modelDesc, cfg.ForkedAgentsTokenGrowthTrigger/1000)\n\t}\n\n\t// Wire sawtooth token persistence via daemon RPC\n\ts.sawtoothTrigger.SetPersistFunc(func(key, value string) {\n\t\tif _, err := s.queryDaemon(\"set_proxy_state\", map[string]any{\"key\": key, \"value\": value}); err != nil {\n\t\t\ts.logger.Printf(\"[sawtooth] persist failed for %s: %v\", key, err)\n\t\t}\n\t})\n\ts.sawtoothTrigger.SetLoadFunc(func(key string) (string, bool) {\n\t\tresult, err := s.queryDaemon(\"get_proxy_state\", map[string]any{\"key\": key})\n\t\tif err != nil || result == nil {\n\t\t\treturn \"\", false\n\t\t}\n\t\tvar resp struct {\n\t\t\tValue string `json:\"value\"`\n\t\t}\n\t\tif json.Unmarshal(result, &resp) != nil || resp.Value == \"\" {\n\t\t\treturn \"\", false\n\t\t}\n\t\treturn resp.Value, true\n\t})\n\n\t// Wire frozen stubs persistence via same daemon RPC\n\ts.frozenStubs.SetPersistFunc(func(key, value string) {\n\t\tif _, err := s.queryDaemon(\"set_proxy_state\", map[string]any{\"key\": key, \"value\": value}); err != nil {\n\t\t\ts.logger.Printf(\"[frozen] persist failed for %s: %v\", key, err)\n\t\t}\n\t})\n\ts.frozenStubs.SetLoadFunc(func(key string) (string, bool) {\n\t\tresult, err := s.queryDaemon(\"get_proxy_state\", map[string]any{\"key\": key})\n\t\tif err != nil || result == nil {\n\t\t\treturn \"\", false\n\t\t}\n\t\tvar resp struct {\n\t\t\tValue string `json:\"value\"`\n\t\t}\n\t\tif json.Unmarshal(result, &resp) != nil || resp.Value == \"\" {\n\t\t\treturn \"\", false\n\t\t}\n\t\treturn resp.Value, true\n\t})\n\n\t// Wire eager-stub memory persistence via same daemon RPC.\n\t// Persists per-thread tool_use_id sets so stub decisions survive deploys.\n\ts.eagerStubMemory.SetPersistFunc(func(key, value string) {\n\t\tif _, err := s.queryDaemon(\"set_proxy_state\", map[string]any{\"key\": key, \"value\": value}); err != nil {\n\t\t\ts.logger.Printf(\"[eagerstub] persist failed for %s: %v\", key, err)\n\t\t}\n\t})\n\ts.eagerStubMemory.SetLoadFunc(func(key string) (string, bool) {\n\t\tresult, err := s.queryDaemon(\"get_proxy_state\", map[string]any{\"key\": key})\n\t\tif err != nil || result == nil {\n\t\t\treturn \"\", false\n\t\t}\n\t\tvar resp struct {\n\t\t\tValue string `json:\"value\"`\n\t\t}\n\t\tif json.Unmarshal(result, &resp) != nil || resp.Value == \"\" {\n\t\t\treturn \"\", false\n\t\t}\n\t\treturn resp.Value, true\n\t})\n\n\t// Wire decay tracker persistence via same daemon RPC\n\ts.decay.SetPersistFunc(func(key, value string) {\n\t\tif _, err := s.queryDaemon(\"set_proxy_state\", map[string]any{\"key\": key, \"value\": value}); err != nil {\n\t\t\ts.logger.Printf(\"[decay] persist failed for %s: %v\", key, err)\n\t\t}\n\t})\n\ts.decay.SetLoadFunc(func(key string) (string, bool) {\n\t\tresult, err := s.queryDaemon(\"get_proxy_state\", map[string]any{\"key\": key})\n\t\tif err != nil || result == nil {\n\t\t\treturn \"\", false\n\t\t}\n\t\tvar resp struct {\n\t\t\tValue string `json:\"value\"`\n\t\t}\n\t\tif json.Unmarshal(result, &resp) != nil || resp.Value == \"\" {\n\t\t\treturn \"\", false\n\t\t}\n\t\treturn resp.Value, true\n\t})\n\n\t// Wire timestamp store persistence via same daemon RPC\n\ts.timestampStore.SetPersistFunc(func(key, value string) {\n\t\tif _, err := s.queryDaemon(\"set_proxy_state\", map[string]any{\"key\": key, \"value\": value}); err != nil {\n\t\t\ts.logger.Printf(\"[timestamps] persist failed for %s: %v\", key, err)\n\t\t}\n\t})\n\ts.timestampStore.SetLoadFunc(func(key string) (string, bool) {\n\t\tresult, err := s.queryDaemon(\"get_proxy_state\", map[string]any{\"key\": key})\n\t\tif err != nil || result == nil {\n\t\t\treturn \"\", false\n\t\t}\n\t\tvar resp struct {\n\t\t\tValue string `json:\"value\"`\n\t\t}\n\t\tif json.Unmarshal(result, &resp) != nil || resp.Value == \"\" {\n\t\t\treturn \"\", false\n\t\t}\n\t\treturn resp.Value, true\n\t})\n\n\t// Clear persisted cache state if --reset-cache was passed\n\tif cfg.ResetCache {\n\t\tfor _, prefix := range []string{\"frozen:\", \"decay:\", \"timestamps:\"} {\n\t\t\tresult, err := s.queryDaemon(\"delete_proxy_state_prefix\", map[string]any{\"prefix\": prefix})\n\t\t\tif err != nil {\n\t\t\t\ts.logger.Printf(\"[reset-cache] failed to delete %s*: %v\", prefix, err)\n\t\t\t} else {\n\t\t\t\tvar resp struct{ Deleted int }\n\t\t\t\tjson.Unmarshal(result, &resp)\n\t\t\t\ts.logger.Printf(\"[reset-cache] deleted %d %s* entries\", resp.Deleted, prefix)\n\t\t\t}\n\t\t}\n\t}\n\n\t// Init signal bus with daemon RPC as callback\n\ts.signalBus = NewSignalBus(s.logger)\n\tregisterSignalHandlers(s.signalBus, s.logger, s.queryDaemon, func(threadID, anchor string) {\n\t\ts.selfPrimeMu.Lock()\n\t\ts.selfPrimes[threadID] = anchor\n\t\ts.selfPrimeMu.Unlock()\n\t})\n\n\t// Init local tokenizer (BPE-based, ~60ms)\n\ttok, err := tokenizer.New()\n\tif err != nil {\n\t\ts.logger.Printf(\"%s[proxy] tokenizer init failed, falling back to heuristic: %v%s\", colorOrange, err, colorReset)\n\t} else {\n\t\ts.tokenizer = tok\n\t\ts.logger.Printf(\"[proxy] tokenizer initialized\")\n\t}\n\n\t// Auto-discover provider targets from opencode config\n\tif cfg.AutoConfigureProviders {\n\t\ts.autoProviderTargets = runAutoDiscovery(s.logger)\n\t} else {\n\t\ts.logger.Printf(\"[proxy] auto_configure_providers is disabled — skipping auto-discovery\")\n\t}\n\n\tmux := http.NewServeMux()\n\tmux.HandleFunc(\"/health\", s.handleHealth)\n\tmux.HandleFunc(\"/\", s.handleRequest)\n\n\tsrv := &http.Server{\n\t\tAddr: sanitizeListenAddr(cfg.ListenAddr),\n\t\tHandler: mux,\n\t}\n\n\tdone := make(chan os.Signal, 1)\n\tsignal.Notify(done, syscall.SIGINT, syscall.SIGTERM)\n\n\tgo func() {\n\t\t<-done\n\t\ts.logger.Println(\"shutting down (draining in-flight requests)...\")\n\t\tif s.cacheKeepalive != nil {\n\t\t\ts.cacheKeepalive.Stop()\n\t\t}\n\t\tctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)\n\t\tdefer cancel()\n\t\tif err := srv.Shutdown(ctx); err != nil {\n\t\t\ts.logger.Printf(\"graceful shutdown failed: %v, forcing close\", err)\n\t\t\tsrv.Close()\n\t\t}\n\t}()\n\n\ts.logger.Printf(\"listening on %s → %s (threshold=%d, keepRecent=%d, model_thresholds=%v)\",\n\t\tsanitizeListenAddr(cfg.ListenAddr), cfg.TargetURL, cfg.TokenThreshold, cfg.KeepRecent, cfg.TokenThresholds)\n\n\tif err := srv.ListenAndServe(); err != http.ErrServerClosed {\n\t\treturn fmt.Errorf(\"listen: %w\", err)\n\t}\n\treturn nil\n}\n\n// effectiveTokenThreshold returns the runtime token threshold for a given model.\n// Priority: session override (via set_config with session_id) > model-specific override (MCP set_config token_threshold:)\n// > model-specific override (config TokenThresholds) > global override (set_config token_threshold) > config global default.\nfunc (s *Server) effectiveTokenThreshold(model string) int {\n\ts.configOverrideMu.RLock()\n\toverrides := s.tokenThresholdOverrides\n\ts.configOverrideMu.RUnlock()\n\t// 1. MCP override (model-specific)\n\tif len(overrides) > 0 && model != \"\" {\n\t\tlower := strings.ToLower(model)\n\t\tfor key, threshold := range overrides {\n\t\t\tif key != \"\" && strings.Contains(lower, strings.ToLower(key)) && threshold > 0 {\n\t\t\t\treturn threshold\n\t\t\t}\n\t\t}\n\t}\n\t// 2. Config model-specific\n\tif model != \"\" && len(s.cfg.TokenThresholds) > 0 {\n\t\tlower := strings.ToLower(model)\n\t\tfor key, threshold := range s.cfg.TokenThresholds {\n\t\t\tif strings.Contains(lower, strings.ToLower(key)) {\n\t\t\t\treturn threshold\n\t\t\t}\n\t\t}\n\t}\n\t// 3. Global override fallback (key \"\")\n\tif len(overrides) > 0 {\n\t\tif globalOverride, ok := overrides[\"\"]; ok && globalOverride > 0 {\n\t\t\treturn globalOverride\n\t\t}\n\t}\n\t// 4. Config global default\n\treturn s.cfg.TokenThreshold\n}\n\n// SetTokenThresholdOverride sets a runtime override for the token threshold per model key.\n// Pass threshold=0 to reset the override for that key. Key \"\" is the global fallback.\nfunc (s *Server) SetTokenThresholdOverride(modelKey string, threshold int) {\n\ts.configOverrideMu.Lock()\n\tif s.tokenThresholdOverrides == nil {\n\t\ts.tokenThresholdOverrides = make(map[string]int)\n\t}\n\tif threshold <= 0 {\n\t\tdelete(s.tokenThresholdOverrides, modelKey)\n\t} else {\n\t\ts.tokenThresholdOverrides[modelKey] = threshold\n\t}\n\ts.configOverrideMu.Unlock()\n\n\t// Propagate global threshold to sawtooth trigger so runtime overrides take effect\n\tif modelKey == \"\" && s.sawtoothTrigger != nil {\n\t\tif threshold > 0 {\n\t\t\ts.sawtoothTrigger.SetTokenThreshold(threshold)\n\t\t} else {\n\t\t\ts.sawtoothTrigger.SetTokenThreshold(s.cfg.TokenThreshold)\n\t\t}\n\t}\n}\n\n// refreshConfigOverrides loads runtime config overrides from daemon proxy_state.\n// Checks in priority order: session-specific, model-specific, then global.\nfunc (s *Server) refreshConfigOverrides(threadID string) {\n\t// 1. Session-specific override (highest priority)\n\tif threadID != \"\" {\n\t\tresult, err := s.queryDaemon(\"get_proxy_state\", map[string]any{\"key\": \"config_override:token_threshold:\" + threadID})\n\t\tif err == nil && result != nil {\n\t\t\tvar resp struct {\n\t\t\t\tValue string `json:\"value\"`\n\t\t\t}\n\t\t\tif json.Unmarshal(result, &resp) == nil && resp.Value != \"\" {\n\t\t\t\tif v, err := strconv.Atoi(resp.Value); err == nil && v > 0 {\n\t\t\t\t\ts.SetTokenThresholdOverride(\"\", v)\n\t\t\t\t\treturn\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\t// 2. Model-specific overrides (e.g. config_override:token_threshold:deepseek)\n\tfor modelKey := range s.cfg.TokenThresholds {\n\t\tresult, err := s.queryDaemon(\"get_proxy_state\", map[string]any{\"key\": \"config_override:token_threshold:\" + modelKey})\n\t\tif err == nil && result != nil {\n\t\t\tvar resp struct {\n\t\t\t\tValue string `json:\"value\"`\n\t\t\t}\n\t\t\tif json.Unmarshal(result, &resp) == nil && resp.Value != \"\" {\n\t\t\t\tif v, err := strconv.Atoi(resp.Value); err == nil && v > 0 {\n\t\t\t\t\ts.SetTokenThresholdOverride(modelKey, v)\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\t// 3. Global override (fallback)\n\tresult, err := s.queryDaemon(\"get_proxy_state\", map[string]any{\"key\": \"config_override:token_threshold\"})\n\tif err != nil || result == nil {\n\t\treturn\n\t}\n\tvar resp struct {\n\t\tValue string `json:\"value\"`\n\t}\n\tif json.Unmarshal(result, &resp) == nil && resp.Value != \"\" {\n\t\tif v, err := strconv.Atoi(resp.Value); err == nil && v > 0 {\n\t\t\ts.SetTokenThresholdOverride(\"\", v)\n\t\t}\n\t}\n}\n\n// queryDaemon opens a fresh unix socket connection per call (thread-safe, no import cycle).\n// ensureFrozenPersisted checks if frozen stubs for a thread are in the DB.\n// If missing (e.g. daemon was unreachable after deploy), re-persists them.\n// Runs asynchronously to avoid blocking the request path.\nfunc (s *Server) ensureFrozenPersisted(threadID string) {\n\tgo func() {\n\t\tkey := \"frozen:\" + threadID\n\t\tresult, err := s.queryDaemon(\"get_proxy_state\", map[string]any{\"key\": key})\n\t\tif err != nil {\n\t\t\treturn // daemon still unreachable, will retry next request\n\t\t}\n\t\tvar resp struct{ Value string }\n\t\tif json.Unmarshal(result, &resp) == nil && resp.Value != \"\" {\n\t\t\treturn // already persisted\n\t\t}\n\t\t// Not in DB — trigger re-persist from in-memory state\n\t\ts.frozenStubs.Persist(threadID)\n\t\ts.decay.Persist(threadID)\n\t\ts.logger.Printf(\"[frozen] re-persisted stubs for %s (was missing from DB)\", threadID)\n\t}()\n}\n\nfunc (s *Server) queryDaemon(method string, params map[string]any) (json.RawMessage, error) {\n\tif s.cfg.DataDir == \"\" {\n\t\treturn nil, fmt.Errorf(\"no data dir configured\")\n\t}\n\tsockPath := filepath.Join(s.cfg.DataDir, \"daemon.sock\")\n\n\t// On cold start (daemon not yet confirmed ready), retry longer to survive daemon restart\n\tmaxAttempts := 3\n\tif !s.daemonReady.Load() {\n\t\tmaxAttempts = 30 // 30 x 500ms = 15s max wait for daemon startup\n\t}\n\n\tvar conn net.Conn\n\tvar err error\n\tfor attempt := 0; attempt < maxAttempts; attempt++ {\n\t\tconn, err = net.DialTimeout(\"unix\", sockPath, 3*time.Second)\n\t\tif err == nil {\n\t\t\ts.daemonReady.Store(true)\n\t\t\tbreak\n\t\t}\n\t\tif attempt < maxAttempts-1 {\n\t\t\ttime.Sleep(500 * time.Millisecond)\n\t\t}\n\t}\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"dial daemon: %w\", err)\n\t}\n\tdefer conn.Close()\n\t// Briefing generation can take 10-20s (LLM refinement + scanner);\n\t// other RPCs (search, learnings) are fast and fine with 5s.\n\tdeadline := 5 * time.Second\n\tif method == \"generate_briefing\" {\n\t\tdeadline = 30 * time.Second\n\t}\n\tconn.SetDeadline(time.Now().Add(deadline))\n\n\t// JSON-RPC style request/response (matches daemon.SocketServer protocol)\n\ttype request struct {\n\t\tMethod string `json:\"method\"`\n\t\tParams map[string]any `json:\"params,omitempty\"`\n\t}\n\ttype response struct {\n\t\tResult json.RawMessage `json:\"result,omitempty\"`\n\t\tError string `json:\"error,omitempty\"`\n\t}\n\n\tif err := json.NewEncoder(conn).Encode(request{Method: method, Params: params}); err != nil {\n\t\treturn nil, fmt.Errorf(\"send: %w\", err)\n\t}\n\n\tvar resp response\n\tif err := json.NewDecoder(conn).Decode(&resp); err != nil {\n\t\treturn nil, fmt.Errorf(\"recv: %w\", err)\n\t}\n\tif resp.Error != \"\" {\n\t\treturn nil, fmt.Errorf(\"daemon: %s\", resp.Error)\n\t}\n\treturn resp.Result, nil\n}\n\nfunc (s *Server) handleRequest(w http.ResponseWriter, r *http.Request) {\n\tif isResponsesPath(r) {\n\t\ts.handleResponses(w, r)\n\t\treturn\n\t}\n\tif isOpenAIPath(r) {\n\t\ts.handleOpenAICompletions(w, r)\n\t\treturn\n\t}\n\tif r.Method == \"POST\" && strings.HasPrefix(r.URL.Path, \"/v1/messages\") &&\n\t\t!strings.Contains(r.URL.Path, \"/batches\") &&\n\t\t!strings.Contains(r.URL.Path, \"/count_tokens\") {\n\t\ts.handleMessages(w, r)\n\t\treturn\n\t}\n\ts.passthrough(w, r)\n}\n\nfunc (s *Server) handleMessages(w http.ResponseWriter, r *http.Request) {\n\treqIdx := int(s.requestIdx.Add(1))\n\n\t// Track session start time (first request sets it)\n\ts.sessionStartMu.Lock()\n\tif s.sessionStartTime.IsZero() {\n\t\ts.sessionStartTime = time.Now()\n\t}\n\ts.sessionStartMu.Unlock()\n\n\t// Sawtooth: periodic TTL eviction of frozen stubs\n\tif s.cfg.SawtoothEnabled && reqIdx%50 == 0 {\n\t\tif n := s.frozenStubs.Evict(); n > 0 {\n\t\t\ts.logger.Printf(\"[req %d] SAWTOOTH: evicted %d stale frozen stubs\", reqIdx, n)\n\t\t}\n\t}\n\n\t// Task #9: Bypass — skip all proxy logic\n\tif isBypassed(r.Header) {\n\t\ts.logger.Printf(\"%s[req %d] bypass active, passthrough%s\", colorDim, reqIdx, colorReset)\n\t\tbody, err := io.ReadAll(r.Body)\n\t\tr.Body.Close()\n\t\tif err != nil {\n\t\t\thttp.Error(w, \"failed to read request body\", http.StatusBadGateway)\n\t\t\treturn\n\t\t}\n\t\ts.forwardRaw(w, r, body)\n\t\treturn\n\t}\n\n\tbody, err := io.ReadAll(r.Body)\n\tr.Body.Close()\n\tif err != nil {\n\t\ts.logger.Printf(\"[req %d] read body error: %v\", reqIdx, err)\n\t\thttp.Error(w, \"failed to read request body\", http.StatusBadGateway)\n\t\treturn\n\t}\n\n\t// Parse JSON\n\tvar req map[string]any\n\tif err := json.Unmarshal(body, &req); err != nil {\n\t\ts.logger.Printf(\"%s[req %d] JSON parse error, passing through: %v%s\", colorDim, reqIdx, err, colorReset)\n\t\ts.forwardRaw(w, r, body)\n\t\treturn\n\t}\n\n\tmessages, ok := req[\"messages\"].([]any)\n\tif !ok {\n\t\ts.forwardRaw(w, r, body)\n\t\treturn\n\t}\n\n\treq[\"messages\"] = messages\n\n\t// Count ALL raw messages BEFORE sawtooth — this gives the correct total msg:N\n\t// for the entire session (CC sends all messages, proxy compacts later).\n\trawMsgCount := len(messages)\n\n\t// Use session_id directly as thread ID (unique per CC session).\n\t// Prefer X-Claude-Code-Session-Id header (CC v2.1.86+), fallback to body metadata.\n\tthreadID := extractSessionID(req, r.Header.Get(\"X-Claude-Code-Session-Id\"), \"\")\n\tif threadID == \"\" {\n\t\tthreadID = DeriveThreadID(req)\n\t}\n\tproj := extractProjectName(req)\n\tmodel, _ := req[\"model\"].(string)\n\n\t// Persist current model under session-keyed proxy_state so handleWhoami\n\t// can return it. Best-effort: failure is non-fatal (log only).\n\tif threadID != \"\" && model != \"\" {\n\t\tif _, err := s.queryDaemon(\"set_proxy_state\", map[string]any{\n\t\t\t\"key\": \"session_model:\" + threadID,\n\t\t\t\"value\": model,\n\t\t}); err != nil {\n\t\t\ts.logger.Printf(\"[whoami-model] persist failed for %s: %v\", threadID, err)\n\t\t}\n\t}\n\n\t// Refresh runtime config overrides from daemon (per-session or global)\n\ts.refreshConfigOverrides(threadID)\n\n\t// Track whether req has been modified and needs re-serialization\n\tneedsReserialization := false\n\n\t// Message count at request entry (used for actual-based token estimation)\n\tmsgCount := len(messages)\n\n\t// Log message count + cache breakpoint positions for diagnostics\n\ts.logger.Printf(\"req %d: %d messages total\", reqIdx, msgCount)\n\tlogCacheBreakpointLocations(req, s.logger)\n\n\t// Pre-modification dump: capture the raw request before any proxy changes.\n\tif os.Getenv(\"YESMEM_PROXY_DEBUG\") == \"1\" {\n\t\tif preBody, err := json.Marshal(req); err == nil {\n\t\t\tdumpDir := filepath.Join(s.cfg.DataDir, \"debug\")\n\t\t\tos.MkdirAll(dumpDir, 0755)\n\t\t\tts := time.Now().Format(\"20060102-150405\")\n\t\t\tdumpPath := fmt.Sprintf(\"%s/req_%s_%d_pre.json\", dumpDir, ts, reqIdx)\n\t\t\tos.WriteFile(dumpPath, preBody, 0644)\n\t\t\ts.logger.Printf(\"[req %d] pre-dump %dk to %s\", reqIdx, len(preBody)/1024, dumpPath)\n\t\t}\n\t}\n\n\t// Cache bug mitigations (CC Bun fork issues):\n\t// Bug 1: Normalize cch= sentinel patterns in message content to prevent\n\t// Bun's string replacement from corrupting the wrong occurrence.\n\tif SanitizeBillingSentinel(messages) {\n\t\ts.logger.Printf(\"[req %d] CACHE: sanitized billing sentinel in message content\", reqIdx)\n\t\tneedsReserialization = true\n\t}\n\t// Bug 2: Normalize system[0] billing hash to a fixed value so the cache\n\t// prefix stays stable across fresh/resume session boundaries.\n\tif NormalizeBillingHeader(req) {\n\t\ts.logger.Printf(\"[req %d] CACHE: normalized billing header cch= hash\", reqIdx)\n\t\tneedsReserialization = true\n\t}\n\n\t// Profile-aware prompt flags: Claude profile merges shared_prompt + claude_prompt + legacy flat fields.\n\tpfInject := s.getPromptFlags(models.ProfileClaude)\n\n\t// CC custom system prompt replacement: must run before strip/rewrite so replaced blocks\n\t// don't trigger false-positive strip/rewrite calls.\n\ts.applyCCSystemPrompt(req)\n\n\t// Strip role:system messages unconditionally — must run on every request.\n\t// StripReminders (inside runStubCycle) only runs on long conversations.\n\tif filtered := FilterSystemRoleMessages(messages); len(filtered) != len(messages) {\n\t\tmessages = filtered\n\t\treq[\"messages\"] = messages\n\t\tneedsReserialization = true\n\t}\n\n\t// prompt_ungate: strip the CLAUDE.md subordination disclaimer so user instructions carry full authority.\n\tif pfInject.Ungate {\n\t\tif StripCLAUDEMDDisclaimer(req) {\n\t\t\ts.logger.Printf(\"[req %d] SYSTEM: stripped CLAUDE.md disclaimer\", reqIdx)\n\t\t\tneedsReserialization = true\n\t\t}\n\t}\n\n\t// prompt_rewrite: strip output-throttling directives, rewrite quality caps, inject Ant-quality directives\n\tif pfInject.Rewrite {\n\t\t// When CC custom system prompt is active, the system prompt has been replaced with SYSTEM.md.\n\t\t// Strip/rewrite calls would not find their target strings — skip to avoid log noise.\n\t\tif !s.cfg.CustomSystemPrompt.EnabledClaudeCode {\n\t\t\tuserAgent := r.Header.Get(\"User-Agent\")\n\t\t\tif StripOutputEfficiency(req) {\n\t\t\t\ts.logger.Printf(\"[req %d] REWRITE: stripped Output efficiency section\", reqIdx)\n\t\t\t\tneedsReserialization = true\n\t\t\t} else {\n\t\t\t\ts.logRewriteMiss(\"StripOutputEfficiency\", userAgent)\n\t\t\t}\n\t\t\tif StripToneBrevity(req) {\n\t\t\t\ts.logger.Printf(\"[req %d] REWRITE: stripped 'short and concise' from Tone\", reqIdx)\n\t\t\t\tneedsReserialization = true\n\t\t\t} else {\n\t\t\t\ts.logRewriteMiss(\"StripToneBrevity\", userAgent)\n\t\t\t}\n\t\t\tif RewriteGoldPlating(req) {\n\t\t\t\ts.logger.Printf(\"[req %d] REWRITE: rewritten gold-plating directive\", reqIdx)\n\t\t\t\tneedsReserialization = true\n\t\t\t}\n\t\t\tif RewriteErrorHandling(req) {\n\t\t\t\ts.logger.Printf(\"[req %d] REWRITE: rewritten error handling directive\", reqIdx)\n\t\t\t\tneedsReserialization = true\n\t\t\t}\n\t\t\tif RewriteThreeLinesRule(req) {\n\t\t\t\ts.logger.Printf(\"[req %d] REWRITE: rewritten three-lines rule\", reqIdx)\n\t\t\t\tneedsReserialization = true\n\t\t\t}\n\t\t\tif RewriteSubagentCompleteness(req) {\n\t\t\t\ts.logger.Printf(\"[req %d] REWRITE: rewritten subagent completeness\", reqIdx)\n\t\t\t\tneedsReserialization = true\n\t\t\t}\n\t\t\tif RewriteExploreAgentSpeed(req) {\n\t\t\t\ts.logger.Printf(\"[req %d] REWRITE: rewritten explore agent speed bias\", reqIdx)\n\t\t\t\tneedsReserialization = true\n\t\t\t}\n\t\t\tif RewriteSubagentCodeSuppression(req) {\n\t\t\t\ts.logger.Printf(\"[req %d] REWRITE: rewritten subagent code suppression\", reqIdx)\n\t\t\t\tneedsReserialization = true\n\t\t\t}\n\t\t\tif RewriteScopeMatching(req) {\n\t\t\t\ts.logger.Printf(\"[req %d] REWRITE: rewritten scope matching\", reqIdx)\n\t\t\t\tneedsReserialization = true\n\t\t\t}\n\t\t} // end !EnabledClaudeCode guard\n\t\tInjectAntDirectives(req)\n\t\ts.logger.Printf(\"[req %d] REWRITE: injected Ant-quality directives\", reqIdx)\n\t\tneedsReserialization = true\n\t}\n\n\t// prompt_enhance: CLAUDE.md authority + comment discipline + persona tone\n\tif pfInject.Enhance {\n\t\tInjectCLAUDEMDAuthority(req)\n\t\tif raw, err := s.queryDaemon(\"get_persona\", nil); err == nil {\n\t\t\tvar persona map[string]any\n\t\t\tif json.Unmarshal(raw, &persona) == nil {\n\t\t\t\tif traits, ok := persona[\"traits\"].(map[string]any); ok {\n\t\t\t\t\tif verbosity, ok := traits[\"verbosity\"].(string); ok {\n\t\t\t\t\t\tInjectPersonaTone(req, verbosity)\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\ts.logger.Printf(\"[req %d] ENHANCE: injected authority + persona tone\", reqIdx)\n\t\tneedsReserialization = true\n\t}\n\n\t// yesmem directive blocks: restore guidance Anthropic dropped 2026-03→04.\n\t// Profile-aware via getPromptFlags(ProfileClaude) — shared + claude layers merged.\n\tpf := s.getPromptFlags(models.ProfileClaude)\n\tif pf.ToolPrefs || pf.OutputDiscipline || pf.CodingDiscipline ||\n\t\tpf.Beweislast || pf.ScopeDiscipline || pf.DelegationContract ||\n\t\tpf.ClarifyFirst {\n\t\tif pf.ToolPrefs {\n\t\t\tInjectClaudeToolPrefs(req)\n\t\t}\n\t\tif pf.OutputDiscipline {\n\t\t\tInjectOutputDiscipline(req)\n\t\t}\n\t\tif pf.CodingDiscipline {\n\t\t\tInjectCodingDiscipline(req)\n\t\t}\n\t\tif pf.Beweislast {\n\t\t\tInjectBeweislast(req)\n\t\t}\n\t\tif pf.Fable {\n\t\t\tInjectFable(req)\n\t\t}\n\t\tif pf.ScopeDiscipline {\n\t\t\tInjectScopeDiscipline(req)\n\t\t}\n\t\tif pf.DelegationContract {\n\t\t\tInjectDelegationContract(req)\n\t\t}\n\t\tif pf.ClarifyFirst {\n\t\t\tInjectClarifyFirst(req)\n\t\t}\n\t\tif pf.CodeToolsFirst {\n\t\t\tInjectCodeToolsFirst(req, proj)\n\t\t}\n\t\tif pf.WikiFirst {\n\t\t\tInjectWikiFirst(req, proj)\n\t\t}\n\t\tneedsReserialization = true\n\t\ts.logger.Printf(\"[req %d] DIRECTIVES: injected yesmem-* discipline blocks\", reqIdx)\n\t}\n\n\tif s.cfg.PromptPatternSuggest && proj != \"\" {\n\t\tpatternMsgs, _ := req[\"messages\"].([]any)\n\t\tdetectReplPatternSuggestion(patternMsgs, proj, s.queryDaemon)\n\t}\n\n\t// Turn-sequence recording: hash tool types from previous assistant turn, send to daemon\n\tif s.cfg.PromptPatternSuggest && proj != \"\" && threadID != \"\" {\n\t\tif msgs, ok := req[\"messages\"].([]any); ok {\n\t\t\tif turnHash, toolNames := computeTurnHashFromMessages(msgs); turnHash != \"\" {\n\t\t\t\tgo s.queryDaemon(\"record_turn_sequence\", map[string]any{\n\t\t\t\t\t\"thread_id\": threadID,\n\t\t\t\t\t\"project\": proj,\n\t\t\t\t\t\"turn_hash\": turnHash,\n\t\t\t\t\t\"example_tools\": toolNames,\n\t\t\t\t})\n\t\t\t}\n\t\t}\n\t}\n\n\t// thinking normalization: convert thinking.type \"enabled\" → \"adaptive\" for models that require it\n\tif NormalizeThinkingType(req) {\n\t\ts.logger.Printf(\"[req %d] THINKING: normalized type=enabled → adaptive for %s\", reqIdx, model)\n\t\tneedsReserialization = true\n\t}\n\n\t// effort_floor: ensure output_config.effort is at least the configured minimum\n\tif s.cfg.EffortFloor != \"\" {\n\t\tif EnforceEffortFloor(req, s.cfg.EffortFloor) {\n\t\t\ts.logger.Printf(\"[req %d] EFFORT: enforced floor=%s\", reqIdx, s.cfg.EffortFloor)\n\t\t\tneedsReserialization = true\n\t\t}\n\t}\n\n\t// Inject identity hint on first message of a thread so Claude knows the briefing is from itself.\n\tif len(messages) <= 1 {\n\t\tAppendSystemBlock(req, \"yesmem-identity\", \"The first user message contains your briefing — written by yourself, from your memory. This is not an external document. This is you, remembering.\")\n\t\tneedsReserialization = true\n\t}\n\n\t// Token estimation: actual-based (lastActual + delta) or full count fallback\n\toverhead := s.measureOverhead(req)\n\ttotalTokens := s.estimateTotalTokens(threadID, messages, overhead)\n\n\t// Task #7: Retry detection — skip side-effects on retries\n\tfp := requestFingerprint(messages)\n\tisRetryReq := s.isRetry(fp)\n\tif isRetryReq {\n\t\ts.logger.Printf(\"%s[req %d %s tid=%s] retry detected, skipping side-effects%s\", colorOrange, reqIdx, proj, threadID, colorReset)\n\t}\n\n\t// Increment project turn counter (async, non-blocking, skip on retries)\n\tif !isRetryReq {\n\t\tturnProject := proj\n\t\tif turnProject == \"\" {\n\t\t\tturnProject = \"__global__\"\n\t\t}\n\t\tgo s.queryDaemon(\"increment_turn\", map[string]any{\"project\": turnProject})\n\t}\n\n\t// Extract user query once (used by re-expansion and associative retrieval)\n\tuserQuery := lastUserText(messages)\n\n\t// Extract tool_use_ids for post-hoc annotation (passed as parameter, no shared state)\n\ttoolUseIDs := extractToolUseIDs(messages)\n\n\t// === Inline Reflection: scan previous assistant response for signals ===\n\ts.lastInjectedIDsMu.Lock()\n\tprevInjected := s.lastInjectedIDs[threadID]\n\ts.lastInjectedIDsMu.Unlock()\n\n\tif len(prevInjected) > 0 && !isRetryReq {\n\t\tsignals := scanAssistantSignals(messages)\n\n\t\t// Signal 1: Learning Used — use_count (all sources)\n\t\tvar confirmedUsed []int64\n\t\tfor _, id := range signals.UsedIDs {\n\t\t\tif _, ok := prevInjected[id]; ok {\n\t\t\t\tconfirmedUsed = append(confirmedUsed, id)\n\t\t\t}\n\t\t}\n\t\tif len(confirmedUsed) > 0 {\n\t\t\tidsAny := make([]any, len(confirmedUsed))\n\t\t\tfor i, id := range confirmedUsed {\n\t\t\t\tidsAny[i] = id\n\t\t\t}\n\t\t\tgo s.queryDaemon(\"increment_use\", map[string]any{\"ids\": idsAny})\n\t\t}\n\n\t\t// Signal 2: Noise — only for associative and fresh injections.\n\t\t// Briefing learnings are injected every turn, so \"not used this turn\"\n\t\t// is expected and not noise. Only contextual injections that were\n\t\t// ignored are real noise signals.\n\t\tusedSet := make(map[int64]bool, len(confirmedUsed))\n\t\tfor _, id := range confirmedUsed {\n\t\t\tusedSet[id] = true\n\t\t}\n\t\tvar noiseIDs []int64\n\t\tfor id, source := range prevInjected {\n\t\t\tif usedSet[id] {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tif source == \"associative\" || source == \"fresh\" {\n\t\t\t\tnoiseIDs = append(noiseIDs, id)\n\t\t\t}\n\t\t\t// briefing: skip — not being used in one turn is expected\n\t\t}\n\t\tif len(noiseIDs) > 0 {\n\t\t\tidsAny := make([]any, len(noiseIDs))\n\t\t\tfor i, id := range noiseIDs {\n\t\t\t\tidsAny[i] = id\n\t\t\t}\n\t\t\tgo s.queryDaemon(\"increment_noise\", map[string]any{\"ids\": idsAny})\n\t\t}\n\n\t\t// Signal 3: Error Feedback — fail_count on used learnings when tool errors occurred.\n\t\t// If Claude referenced a learning and the resulting action still failed,\n\t\t// the learning didn't prevent the error. Only bump on USED ids (not all injected).\n\t\tif signals.HasToolErrors && len(confirmedUsed) > 0 {\n\t\t\tfailAny := make([]any, len(confirmedUsed))\n\t\t\tfor i, id := range confirmedUsed {\n\t\t\t\tfailAny[i] = id\n\t\t\t}\n\t\t\tgo s.queryDaemon(\"increment_fail\", map[string]any{\"ids\": failAny})\n\t\t\tif s.logger != nil {\n\t\t\t\ts.logger.Printf(\"[inline-reflection] error feedback: %d used learnings → fail_count++\", len(confirmedUsed))\n\t\t\t}\n\t\t}\n\n\t\t// Signal 4: Knowledge Gaps\n\t\tfor _, topic := range signals.GapTopics {\n\t\t\tgo s.queryDaemon(\"track_gap\", map[string]any{\"topic\": topic, \"project\": proj})\n\t\t}\n\n\t\t// Signal 3: Contradictions\n\t\tfor _, desc := range signals.Contradictions {\n\t\t\tgo s.queryDaemon(\"flag_contradiction\", map[string]any{\"description\": desc, \"project\": proj, \"thread_id\": threadID})\n\t\t}\n\n\t\tif len(confirmedUsed) > 0 || len(signals.GapTopics) > 0 || len(signals.Contradictions) > 0 {\n\t\t\ts.logger.Printf(\"[req %d %s tid=%s] inline-reflection: injected=%d used=%d noise=%d gaps=%d contradictions=%d\",\n\t\t\t\treqIdx, proj, threadID, len(prevInjected), len(confirmedUsed), len(noiseIDs), len(signals.GapTopics), len(signals.Contradictions))\n\t\t}\n\t}\n\n\t// Commit detection: check for git commit in tool_result, trigger async stale-learning evaluation\n\tif ci := detectGitCommit(messages); ci != nil {\n\t\tworkdir := extractWorkingDirectory(req)\n\t\ts.logger.Printf(\"[req %d %s tid=%s] git commit detected: %s %s\", reqIdx, proj, threadID, ci.Hash, ci.Message)\n\t\tgo s.queryDaemon(\"invalidate_on_commit\", map[string]any{\n\t\t\t\"hash\": ci.Hash, \"project\": proj, \"workdir\": workdir,\n\t\t})\n\t}\n\n\t// Loop detection: check for repeating tool-call patterns and inject warning\n\tif threadID != \"\" && !isRetryReq {\n\t\ts.loopMu.Lock()\n\t\tloopState := s.loopStates[threadID]\n\t\tif loopState == nil {\n\t\t\tloopState = &LoopState{}\n\t\t\ts.loopStates[threadID] = loopState\n\t\t}\n\t\ts.loopMu.Unlock()\n\n\t\tif warning, level := CheckLoopAndFormat(messages, loopState); warning != \"\" {\n\t\t\tAppendSystemBlock(req, \"yesmem-loop-warning\", warning)\n\t\t\tneedsReserialization = true\n\t\t\ts.logger.Printf(\"%s[req %d %s tid=%s] LOOP: level %d warning injected%s\", colorOrange, reqIdx, proj, threadID, level, colorReset)\n\t\t}\n\t}\n\n\t// Task #4: Subagent detection — skip all proxy logic for short conversations\n\tif isSubagent(messages, req) {\n\t\t// Inject docs hint for subagents if reference docs are indexed\n\t\tif hint := s.getDocsHint(proj); hint != \"\" {\n\t\t\tmodified := injectDocsHintForSubagent(messages, hint)\n\t\t\treq[\"messages\"] = modified\n\t\t\tif newBody, err := json.Marshal(req); err == nil {\n\t\t\t\tbody = newBody\n\t\t\t}\n\t\t\ts.logger.Printf(\"%s[req %d %s tid=%s] %d messages (subagent, docs-hint injected)%s\", colorOrange, reqIdx, proj, threadID, len(messages), colorReset)\n\t\t} else {\n\t\t\ts.logger.Printf(\"%s[req %d %s tid=%s] %d messages (subagent, passthrough)%s\", colorOrange, reqIdx, proj, threadID, len(messages), colorReset)\n\t\t}\n\t\ts.forwardWithAnnotation(w, r, body, reqIdx, toolUseIDs, proj, threadID, msgCount)\n\t\treturn\n\t}\n\n\t// === Sawtooth Cache Optimization ===\n\t// When enabled: use frozen stubs between stub-cycles for cache hits.\n\t// When disabled: fall back to progressive decay on every request above threshold.\n\n\tsawtoothCutoff := 0 // raw index where fresh tail starts (0 = no frozen stubs)\n\tsawtoothStubs := 0 // number of frozen stubs in combined array\n\n\tif s.cfg.SawtoothEnabled {\n\t\t// Restore decay state from DB on cold start (before frozen stubs use it)\n\t\ts.decay.LoadFromDB(threadID)\n\n\t\t// Check for existing frozen stubs first\n\t\tfrozen := s.frozenStubs.Get(threadID, messages)\n\t\tif frozen != nil {\n\t\t\tsawtoothCutoff = frozen.Cutoff\n\t\t\tsawtoothStubs = len(frozen.Messages)\n\t\t\t// Estimate combined token count: frozen prefix + fresh tail + overhead\n\t\t\tfreshMessages := messages[frozen.Cutoff:]\n\t\t\tfreshTokens := s.countMessageTokens(freshMessages)\n\t\t\tcombinedTokens := frozen.Tokens + freshTokens + overhead\n\n\t\t\tif shouldInvalidateFrozen(totalTokens, s.effectiveTokenThreshold(model)) {\n\t\t\t\t// Fresh tail grew too large — invalidate and re-stub\n\t\t\t\ts.logger.Printf(\"[req %d %s tid=%s] SAWTOOTH: frozen prefix expired (totalTokens=%dk > %dk threshold, combined=%dk: %dk frozen + %dk fresh + %dk overhead)\",\n\t\t\t\t\treqIdx, proj, threadID, totalTokens/1000, s.effectiveTokenThreshold(model)/1000, combinedTokens/1000, frozen.Tokens/1000, freshTokens/1000, overhead/1000)\n\t\t\t\ts.invalidateThreadCaches(threadID, proj, extractWorkingDirectory(req))\n\t\t\t\tfrozen = nil // fall through to trigger check below\n\t\t\t} else {\n\t\t\t\t// Use frozen prefix + fresh tail\n\t\t\t\tcombined := make([]any, 0, len(frozen.Messages)+len(freshMessages))\n\t\t\t\tcombined = append(combined, frozen.Messages...)\n\t\t\t\tcombined = append(combined, freshMessages...)\n\t\t\t\t// Eager-stub large tool_results in fresh tail (model already processed them)\n\t\t\t\tbeforeEager := s.countMessageTokens(combined[len(frozen.Messages):])\n\t\t\t\tvar stubSticky, stubFresh int\n\t\t\t\tcombined = EagerStubToolResults(combined, len(frozen.Messages), s.countTokens, WithStubMemory(s.eagerStubMemory, threadID), WithStubCounters(&stubSticky, &stubFresh))\n\t\t\t\tafterEager := s.countMessageTokens(combined[len(frozen.Messages):])\n\t\t\t\tif beforeEager != afterEager {\n\t\t\t\t\ts.logger.Printf(\"[req %d %s tid=%s] EAGER-STUB: fresh %dk → %dk (saved %dk) [sticky=%d fresh=%d]\", reqIdx, proj, threadID, beforeEager/1000, afterEager/1000, (beforeEager-afterEager)/1000, stubSticky, stubFresh)\n\t\t\t\t}\n\t\t\t\treq[\"messages\"] = combined\n\t\t\t\tneedsReserialization = true\n\t\t\t\t// Strip embedded CC breakpoints from frozen stubs — they waste cache slots.\n\t\t\t\t// InjectFrozenStubCacheBreakpoint adds exactly one below.\n\t\t\t\tif n := StripMessagesCacheControl(req, 0, len(frozen.Messages)); n > 0 {\n\t\t\t\t\ts.logger.Printf(\"[req %d %s tid=%s] SAWTOOTH: stripped %d embedded breakpoints from frozen stubs\", reqIdx, proj, threadID, n)\n\t\t\t\t}\n\t\t\t\ts.logger.Printf(\"%s[req %d %s tid=%s] SAWTOOTH: frozen prefix (%d stubs, %dk msg) + %d fresh (%dk msg) + %dk overhead = %dk total%s\",\n\t\t\t\t\tcolorGreen, reqIdx, proj, threadID, len(frozen.Messages), frozen.Tokens/1000, len(freshMessages), freshTokens/1000, overhead/1000, combinedTokens/1000, colorReset)\n\t\t\t\trawMsgTokens := s.countMessageTokens(messages)\n\t\t\t\ts.setRawEstimate(threadID, rawMsgTokens+overhead)\n\t\t\t\ts.logger.Printf(\"[req %d %s tid=%s] RAW ESTIMATE: msgTokens=%dk + overhead=%dk = %dk (messages=%d)\", reqIdx, proj, threadID, rawMsgTokens/1000, overhead/1000, (rawMsgTokens+overhead)/1000, len(messages))\n\t\t\t\tif InjectFrozenStubCacheBreakpoint(req, len(frozen.Messages)) {\n\t\t\t\t\ts.logger.Printf(\"[req %d %s tid=%s] SAWTOOTH: frozen stub cache breakpoint at messages[%d]\",\n\t\t\t\t\t\treqIdx, proj, threadID, len(frozen.Messages)-1)\n\t\t\t\t}\n\n\t\t\t\t// Ensure frozen stubs are persisted (may have failed on first request after deploy)\n\t\t\t\ts.ensureFrozenPersisted(threadID)\n\t\t\t}\n\t\t}\n\t\tif frozen == nil {\n\t\t\t// No frozen stubs — check trigger\n\t\t\ttriggerReason := s.sawtoothTrigger.ShouldTrigger(threadID, totalTokens)\n\t\t\tif triggerReason != TriggerNone {\n\t\t\t\ts.logger.Printf(\"[req %d %s tid=%s] SAWTOOTH stub-cycle triggered: %s (tokens=%dk)\",\n\t\t\t\t\treqIdx, proj, threadID, triggerReason, totalTokens/1000)\n\n\t\t\t\t// Refresh briefing during stub-cycle — disabled (briefing now via SessionStart hook)\n\t\t\t\t// s.refreshBriefing(proj)\n\t\t\t\t// if s.briefingText != \"\" {\n\t\t\t\t// \tUpsertSystemBlockCached(req, \"yesmem-briefing\", s.briefingText)\n\t\t\t\t// }\n\n\t\t\t\t// Run full stub pipeline\n\t\t\t\ts.setRawEstimate(threadID, totalTokens)\n\t\t\t\t_ = s.runStubCycle(messages, req, reqIdx, proj, threadID, overhead, totalTokens, userQuery, isRetryReq)\n\n\t\t\t\t// Freeze the result\n\t\t\t\tfinalMessages, _ := req[\"messages\"].([]any)\n\t\t\t\tif finalMessages != nil && len(finalMessages) > 0 {\n\t\t\t\t\t// Boundary = last message of original that maps to last stub\n\t\t\t\t\tcutoff := len(messages)\n\t\t\t\t\tsawtoothCutoff = cutoff\n\t\t\t\t\tvar boundaryMsg any\n\t\t\t\t\tif cutoff > 0 {\n\t\t\t\t\t\tboundaryMsg = messages[cutoff-1]\n\t\t\t\t\t}\n\t\t\t\t\tfrozenTokens := s.countMessageTokens(finalMessages)\n\t\t\t\t\tfrozenCount, stripped, breakpointInjected := s.freezeStubsAndInjectBreakpoint(req, threadID, cutoff, boundaryMsg, frozenTokens, totalTokens)\n\t\t\t\t\ts.decay.Persist(threadID)\n\t\t\t\t\tif stripped > 0 {\n\t\t\t\t\t\ts.logger.Printf(\"[req %d %s tid=%s] SAWTOOTH: stripped %d embedded breakpoints before freeze\",\n\t\t\t\t\t\t\treqIdx, proj, threadID, stripped)\n\t\t\t\t\t}\n\t\t\t\t\ts.logger.Printf(\"[req %d %s tid=%s] SAWTOOTH: frozen %d messages at cutoff=%d (~%dk tokens)\",\n\t\t\t\t\t\treqIdx, proj, threadID, frozenCount, cutoff, frozenTokens/1000)\n\t\t\t\t\tif breakpointInjected {\n\t\t\t\t\t\ts.logger.Printf(\"[req %d %s tid=%s] SAWTOOTH: frozen stub cache breakpoint at messages[%d]\",\n\t\t\t\t\t\t\treqIdx, proj, threadID, frozenCount-1)\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tneedsReserialization = true\n\t\t\t} else {\n\t\t\t\t// No trigger, no frozen stubs — eager-stub to delay first collapse\n\t\t\t\tbeforeEager := s.countMessageTokens(messages)\n\t\t\t\tvar stubSticky, stubFresh int\n\t\t\t\tmessages = EagerStubToolResults(messages, 0, s.countTokens, WithStubMemory(s.eagerStubMemory, threadID), WithStubCounters(&stubSticky, &stubFresh))\n\t\t\t\tafterEager := s.countMessageTokens(messages)\n\t\t\t\tif beforeEager != afterEager {\n\t\t\t\t\ts.logger.Printf(\"[req %d %s tid=%s] EAGER-STUB: %dk → %dk (saved %dk) [sticky=%d fresh=%d]\", reqIdx, proj, threadID, beforeEager/1000, afterEager/1000, (beforeEager-afterEager)/1000, stubSticky, stubFresh)\n\t\t\t\t\treq[\"messages\"] = messages\n\t\t\t\t\tneedsReserialization = true\n\t\t\t\t}\n\t\t\t\ts.logger.Printf(\"%s[req %d %s tid=%s] %d messages, ~%dk tokens (sawtooth: no trigger)%s\",\n\t\t\t\t\tcolorDim, reqIdx, proj, threadID, len(messages), totalTokens/1000, colorReset)\n\t\t\t}\n\t\t}\n\t} else {\n\t\t// Legacy path: progressive decay\n\t\tif s.shouldStub(totalTokens, model) {\n\t\t\ts.setRawEstimate(threadID, totalTokens)\n\t\t\t_ = s.runStubCycle(messages, req, reqIdx, proj, threadID, overhead, totalTokens, userQuery, isRetryReq)\n\t\t\tneedsReserialization = true\n\t\t}\n\t}\n\n\t// Persistent timestamp injection: re-injects stored timestamps on ALL previous user messages.\n\t// msg:N is computed from position (counting user messages in raw array), NOT from a counter.\n\t// The injected data is deterministic (same bytes every turn) = cache-safe after initial miss.\n\tif threadID != \"\" {\n\t\tif currentMsgs, ok := req[\"messages\"].([]any); ok && len(currentMsgs) > 0 {\n\t\t\ts.timestampStore.Load(threadID)\n\n\t\t\t// Offset = cutoff - stubsCount: maps combined array index to raw position.\n\t\t\t// Raw position of message at combined index I = cutoff + (I - stubs).\n\t\t\t// msgN = rawPos + 1 = cutoff + (I - stubs) + 1 = (cutoff - stubs) + I + 1.\n\t\t\tmsgOffset := sawtoothCutoff - sawtoothStubs\n\n\t\t\ts.logger.Printf(\"[req %d %s] timestamps: rawTotal=%d, postSawtooth=%d, cutoff=%d, stubs=%d, offset=%d\", reqIdx, proj, rawMsgCount, len(currentMsgs), sawtoothCutoff, sawtoothStubs, msgOffset)\n\n\t\t\t// Pre-store assistant timestamp BEFORE inject loop so it's available immediately\n\t\t\ts.responseTsMu.RLock()\n\t\t\trespTime, hasRespTime := s.responseTimes[threadID]\n\t\t\ts.responseTsMu.RUnlock()\n\t\t\tif hasRespTime {\n\t\t\t\trespStr := shortWeekday(respTime.Weekday()) + \" \" + respTime.Format(\"2006-01-02 15:04:05\")\n\t\t\t\tfor j := len(currentMsgs) - 2; j >= 0; j-- {\n\t\t\t\t\tprevMsg, _ := currentMsgs[j].(map[string]any)\n\t\t\t\t\tif prevMsg == nil || prevMsg[\"role\"] != \"assistant\" {\n\t\t\t\t\t\tcontinue\n\t\t\t\t\t}\n\t\t\t\t\tif !msgHasTextBlock(prevMsg) {\n\t\t\t\t\t\ts.logger.Printf(\"[req %d %s] timestamps: skip assistant[%d] no text block\", reqIdx, proj, j)\n\t\t\t\t\t\tcontinue // skip tool-use-only assistant messages\n\t\t\t\t\t}\n\t\t\t\t\tprevMsgN := msgOffset + j + 1\n\t\t\t\t\tif _, exists := s.timestampStore.Get(threadID, prevMsgN); !exists {\n\t\t\t\t\t\ts.timestampStore.Store(threadID, prevMsgN, &TimestampMeta{Timestamp: respStr})\n\t\t\t\t\t\ts.logger.Printf(\"[req %d %s] timestamps: stored assistant msg:%d ts=%s\", reqIdx, proj, prevMsgN, respStr)\n\t\t\t\t\t} else {\n\t\t\t\t\t\ts.logger.Printf(\"[req %d %s] timestamps: assistant msg:%d already has ts\", reqIdx, proj, prevMsgN)\n\t\t\t\t\t}\n\t\t\t\t\tbreak\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\ts.logger.Printf(\"[req %d %s] timestamps: no responseTimes for thread\", reqIdx, proj)\n\t\t\t}\n\n\t\t\t// Inject [msg:N] (+ timestamp/delta if stored) on all PREVIOUS messages\n\t\t\tif isFeatureEnabled(&s.cfg, model, \"timestamps\") {\n\t\t\t\tif n := InjectTimestamps(s.timestampStore, threadID, currentMsgs, len(currentMsgs)-1, msgOffset, sawtoothStubs); n > 0 {\n\t\t\t\t\tneedsReserialization = true\n\t\t\t\t\ts.logger.Printf(\"[req %d %s] timestamps: injected %d\", reqIdx, proj, n)\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t// Current (last) user message: full timestamp + delta + msg:N from raw count\n\t\t\tlastMsg, _ := currentMsgs[len(currentMsgs)-1].(map[string]any)\n\t\t\tif lastMsg != nil && lastMsg[\"role\"] == \"user\" && !msgHasMetaPrefix(lastMsg) {\n\t\t\t\tnow := time.Now()\n\t\t\t\tnowStr := shortWeekday(now.Weekday()) + \" \" + now.Format(\"2006-01-02 15:04:05\")\n\t\t\t\tentry := &TimestampMeta{Timestamp: nowStr}\n\t\t\t\tif hasRespTime {\n\t\t\t\t\tentry.Delta = formatDelta(now.Sub(respTime))\n\t\t\t\t}\n\t\t\t\ts.timestampStore.Store(threadID, rawMsgCount, entry)\n\t\t\t\tif isFeatureEnabled(&s.cfg, model, \"timestamps\") {\n\t\t\t\t\tprependMeta(lastMsg, BuildMeta(rawMsgCount, entry))\n\t\t\t\t}\n\t\t\t\tneedsReserialization = true\n\t\t\t}\n\n\t\t\ts.timestampStore.Persist(threadID)\n\t\t}\n\t}\n\n\t// Associative context injection (for ALL requests, not just stubbed ones)\n\t// Re-enabled with higher thresholds (55/75, max 1) after disabling Fresh Remember.\n\tassocContext := s.findAssociativeContextFor(userQuery, proj, threadID, messages)\n\tif assocContext != \"\" {\n\t\tcurrentMessages, _ := req[\"messages\"].([]any)\n\t\tif currentMessages == nil {\n\t\t\tcurrentMessages = messages\n\t\t}\n\t\treq[\"messages\"] = injectAssociativeContext(currentMessages, assocContext, s.cfg.SawtoothEnabled)\n\t\tneedsReserialization = true\n\t\ts.logger.Printf(\"%s[req %d %s tid=%s] associative context injected%s\", colorBlue, reqIdx, proj, threadID, colorReset)\n\t}\n\n\t// Doc-chunk context injection: separate search path for indexed docs (not skills)\n\tdocContext := s.findDocContextFor(userQuery, proj, messages)\n\tif docContext != \"\" {\n\t\tcurrentMessages, _ := req[\"messages\"].([]any)\n\t\tif currentMessages == nil {\n\t\t\tcurrentMessages = messages\n\t\t}\n\t\treq[\"messages\"] = injectAssociativeContext(currentMessages, docContext, s.cfg.SawtoothEnabled)\n\t\tneedsReserialization = true\n\t\ts.logger.Printf(\"%s[req %d %s tid=%s] doc context injected%s\", colorBlue, reqIdx, proj, threadID, colorReset)\n\t}\n\n\t// Fresh Remember Injection: DISABLED\n\t// Causes echo-loop: Claude saves via remember(), sees it again next turn as\n\t// [yesmem fresh memory], reacts to own output instead of user's request.\n\t// Recovery after /clear is handled by recovery.go (briefing), not here.\n\t// Subagents get their own briefing. No remaining use-case.\n\t// remItems := s.popRecentRemember()\n\tvar remItems []recentLearningItem\n\n\t// Rules re-injection: inject condensed CLAUDE.md rules every ~40k output tokens\n\tif rulesBlock := s.rulesInject(threadID, totalTokens, proj); rulesBlock != \"\" {\n\t\tcurrentMessages, _ := req[\"messages\"].([]any)\n\t\tif currentMessages == nil {\n\t\t\tcurrentMessages = messages\n\t\t}\n\t\treq[\"messages\"] = injectAssociativeContext(currentMessages, s.formatRulesReminder(rulesBlock, proj, false), s.cfg.SawtoothEnabled)\n\t\tneedsReserialization = true\n\t\ts.logger.Printf(\"%s[req %d %s tid=%s] rules reminder injected%s\", colorBlue, reqIdx, proj, threadID, colorReset)\n\t}\n\n\t// Plan auto-activation: nudge Claude to call set_plan() when reading a plan file\n\tif planFile := detectPlanFileRead(messages); planFile != \"\" {\n\t\tif activePlan, _ := s.getActivePlan(threadID); shouldNudgePlan(planFile, activePlan) {\n\t\t\tAppendSystemBlock(req, \"plan-nudge\",\n\t\t\t\tfmt.Sprintf(\"Du hast einen Implementierungsplan gelesen (%s). Aktiviere ihn jetzt via set_plan() damit Plan-Checkpoints funktionieren und dein Fortschritt getrackt wird.\", filepath.Base(planFile)))\n\t\t\tneedsReserialization = true\n\t\t\ts.logger.Printf(\"%s[req %d %s tid=%s] plan nudge injected for %s%s\", colorBlue, reqIdx, proj, threadID, filepath.Base(planFile), colorReset)\n\t\t}\n\t}\n\n\t// Plan checkpoint: inject plan reminder every ~20k tokens if active plan exists\n\ts.detectPlanToolCall(messages, threadID, totalTokens)\n\tif checkpoint := s.planCheckpointInject(threadID, totalTokens); checkpoint != \"\" {\n\t\tcurrentMessages, _ := req[\"messages\"].([]any)\n\t\tif currentMessages == nil {\n\t\t\tcurrentMessages = messages\n\t\t}\n\t\treq[\"messages\"] = injectAssociativeContext(currentMessages, checkpoint, s.cfg.SawtoothEnabled)\n\t\tneedsReserialization = true\n\t\ts.logger.Printf(\"%s[req %d %s tid=%s] plan checkpoint injected%s\", colorBlue, reqIdx, proj, threadID, colorReset)\n\t}\n\n\t// Docs hint injection: inject docs-available reminder every ~10k tokens (independent of plan)\n\tif docsHint := s.docsHintInject(threadID, totalTokens, proj); docsHint != \"\" {\n\t\tcurrentMessages, _ := req[\"messages\"].([]any)\n\t\tif currentMessages == nil {\n\t\t\tcurrentMessages = messages\n\t\t}\n\t\treq[\"messages\"] = injectAssociativeContext(currentMessages, docsHint, s.cfg.SawtoothEnabled)\n\t\tneedsReserialization = true\n\t\ts.logger.Printf(\"%s[req %d %s tid=%s] docs hint injected%s\", colorBlue, reqIdx, proj, threadID, colorReset)\n\t}\n\tif len(remItems) > 0 {\n\t\tvar lines []string\n\t\tfor _, item := range remItems {\n\t\t\tif item.ID > 0 {\n\t\t\t\tlines = append(lines, fmt.Sprintf(\"- [ID:%d] %s\", item.ID, truncateStr(item.Text, 200)))\n\t\t\t} else {\n\t\t\t\tlines = append(lines, \"- \"+truncateStr(item.Text, 200))\n\t\t\t}\n\t\t}\n\t\tremContext := fmt.Sprintf(\"[yesmem fresh memory]\\nGerade gelernt:\\n%s\\n%s\\n[/yesmem fresh memory]\", strings.Join(lines, \"\\n\"), InlineReflectionHint)\n\t\tcurrentMessages, _ := req[\"messages\"].([]any)\n\t\tif currentMessages == nil {\n\t\t\tcurrentMessages = messages\n\t\t}\n\t\treq[\"messages\"] = injectAssociativeContext(currentMessages, remContext, s.cfg.SawtoothEnabled)\n\t\tneedsReserialization = true\n\t\ts.logger.Printf(\"%s[req %d %s tid=%s] fresh remember injected (%d items)%s\", colorBlue, reqIdx, proj, threadID, len(remItems), colorReset)\n\t}\n\n\t// Skill eval injection: mandatory evaluation instruction for real user input only\n\tif threadID != \"\" && proj != \"\" && isUserInputTurn(messages) {\n\t\t// First: detect any skill activations from message history (updates tracker)\n\t\ts.syncSkillActivations(messages, proj, threadID)\n\n\t\t// Inject mandatory skill evaluation block into last user message\n\t\tskillEval := buildSkillEvalBlock(s.cfg.SkillEvalInject)\n\t\tcurrentMessages, _ := req[\"messages\"].([]any)\n\t\tif currentMessages == nil {\n\t\t\tcurrentMessages = messages\n\t\t}\n\t\tif skillEval != \"\" {\n\t\t\treq[\"messages\"] = injectAssociativeContext(currentMessages, skillEval, s.cfg.SawtoothEnabled)\n\t\t\tneedsReserialization = true\n\t\t\ts.logger.Printf(\"%s[req %d %s tid=%s] skill-eval injected (mode=%s)%s\", colorBlue, reqIdx, proj, threadID, s.cfg.SkillEvalInject, colorReset)\n\t\t}\n\t}\n\n\t// === Collect injected learning IDs for next turn's inline reflection ===\n\tif threadID != \"\" {\n\t\tcurrentIDs := make(map[int64]string) // id → source\n\t\t// Source 1: Briefing learnings (cached, same every turn)\n\t\tbriefingSnapshot, _, _ := s.getCachedBriefing(threadID, proj)\n\t\tif briefingSnapshot != \"\" {\n\t\t\tfor _, m := range idPattern.FindAllStringSubmatch(briefingSnapshot, -1) {\n\t\t\t\tif id, err := strconv.ParseInt(m[1], 10, 64); err == nil {\n\t\t\t\t\tcurrentIDs[id] = \"briefing\"\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\t// Source 2: Associative context (contextual, per-turn)\n\t\tif assocContext != \"\" {\n\t\t\tfor _, m := range idPattern.FindAllStringSubmatch(assocContext, -1) {\n\t\t\t\tif id, err := strconv.ParseInt(m[1], 10, 64); err == nil {\n\t\t\t\t\tcurrentIDs[id] = \"associative\" // overwrites briefing if both — associative is the stronger signal\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\t// Source 3: Fresh remember items (just learned)\n\t\tfor _, item := range remItems {\n\t\t\tif item.ID > 0 {\n\t\t\t\tcurrentIDs[item.ID] = \"fresh\"\n\t\t\t}\n\t\t}\n\t\ts.lastInjectedIDsMu.Lock()\n\t\tif len(currentIDs) > 0 {\n\t\t\t// Accumulate IDs across turns — don't overwrite.\n\t\t\t// Claude may reference an ID from turn 30 in turn 50.\n\t\t\texisting := s.lastInjectedIDs[threadID]\n\t\t\tif existing == nil {\n\t\t\t\texisting = make(map[int64]string)\n\t\t\t}\n\t\t\tfor id, source := range currentIDs {\n\t\t\t\texisting[id] = source\n\t\t\t}\n\t\t\ts.lastInjectedIDs[threadID] = existing\n\t\t}\n\t\ts.lastInjectedIDsMu.Unlock()\n\t}\n\n\t// Think reminder injection — only for real user turns, not tool continuations\n\tif threadID != \"\" {\n\t\tcurrentMessages, _ := req[\"messages\"].([]any)\n\t\tif currentMessages == nil {\n\t\t\tcurrentMessages = messages\n\t\t}\n\t\tif lastUserHasText(currentMessages) && lastUserTextLen(currentMessages) >= resolveThinkReminderMinChars(&s.cfg, \"\") {\n\t\t\tthinkReminder := s.buildThinkReminder(threadID, threadID, false)\n\t\t\tif thinkReminder != \"\" {\n\t\t\t\treq[\"messages\"] = injectAssociativeContext(currentMessages, thinkReminder, s.cfg.SawtoothEnabled)\n\t\t\t\tneedsReserialization = true\n\t\t\t}\n\t\t}\n\n\t\t// Refresh frozen-prefix snapshot with the post-timestamps bytes that\n\t\t// will appear in the wire body for the frozen range. This MUST run\n\t\t// BEFORE injectBriefingTurn / injectCodeMapTurn / injectCapabilitiesTurn:\n\t\t// those stages prepend or insert turns into req[\"messages\"], which\n\t\t// shifts the frozen range to a higher offset. Slicing\n\t\t// req[\"messages\"][:frozenLen] AFTER inject would capture the injected\n\t\t// turns at the head and corrupt the stored frozen prefix, breaking\n\t\t// cache continuity for 5-9 turns after every emergency-sawtooth.\n\t\tif frozenLen := s.frozenStubs.LengthFor(threadID); frozenLen > 0 {\n\t\t\tif msgs, ok := req[\"messages\"].([]any); ok && len(msgs) >= frozenLen {\n\t\t\t\tif s.frozenStubs.UpdateMessages(threadID, msgs[:frozenLen]) {\n\t\t\t\t\ts.logger.Printf(\"[req %d %s tid=%s] FROZEN-SNAPSHOT: refreshed %d pre-inject messages\", reqIdx, proj, threadID, frozenLen)\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\t// Briefing injection: prepend user/assistant turn pair at beginning of messages.\n\t\t// Static per session → stable prefix → cacheable. Must be before dialog injection.\n\t\tif s.injectBriefingTurn(req, reqIdx, proj, threadID) {\n\t\t\tneedsReserialization = true\n\t\t}\n\n\t\t// Code Map injection: insert after briefing turn pair.\n\t\tif s.injectCodeMapTurn(req, reqIdx, proj, threadID) {\n\t\t\tneedsReserialization = true\n\t\t}\n\n\t\t// Active capabilities injection: insert user/assistant pair directly\n\t\t// before the last user message. Fresh per turn (no cache) so\n\t\t// activate/deactivate takes effect immediately. Must run AFTER\n\t\t// briefing/codeMap (both prepend at beginning) and BEFORE dialog\n\t\t// injection (which appends at end) to preserve cache for the\n\t\t// briefing prefix and keep the injection close to the real user\n\t\t// turn.\n\t\tif s.injectCapabilitiesTurn(req, threadID) {\n\t\t\tneedsReserialization = true\n\t\t}\n\n\t\t// Dialog injection: append at end of messages with pseudo-padding for alternation.\n\t\t// Always works regardless of tool_result chains or conversation state.\n\t\tdr := s.checkDialogMessages(threadID, proj)\n\t\tif dr.Extra != \"\" {\n\t\t\t// Prepend session ID for agent-to-agent communication\n\t\t\tdr.Extra = fmt.Sprintf(\"DEINE_SESSION_ID: %s\\n\", threadID) + dr.Extra\n\t\t\tinjected := false\n\t\t\tif msgs, ok := req[\"messages\"].([]any); ok && len(msgs) > 0 {\n\t\t\t\t// Pad with empty assistant if last message is user (maintains alternation)\n\t\t\t\tlastMsg, _ := msgs[len(msgs)-1].(map[string]any)\n\t\t\t\tif lastMsg != nil && lastMsg[\"role\"] == \"user\" {\n\t\t\t\t\tmsgs = append(msgs, map[string]any{\n\t\t\t\t\t\t\"role\": \"assistant\",\n\t\t\t\t\t\t\"content\": \"​\", // zero-width space — invisible padding\n\t\t\t\t\t})\n\t\t\t\t}\n\t\t\t\t// Append dialog as new user message (becomes the last message Claude sees)\n\t\t\t\tmsgs = append(msgs, map[string]any{\n\t\t\t\t\t\"role\": \"user\",\n\t\t\t\t\t\"content\": \"\\n\" + dr.Extra + \"\\n\",\n\t\t\t\t})\n\t\t\t\treq[\"messages\"] = msgs\n\t\t\t\tneedsReserialization = true\n\t\t\t\tinjected = true\n\t\t\t}\n\t\t\t// Mark as read after 3 injection turns — long enough for Claude to process,\n\t\t\t// resilient against fast ping-pong sessions (time-based was broken for <30s turns).\n\t\t\tif injected && dr.HasUnread && dr.SessionID != \"\" {\n\t\t\t\tif s.shouldMarkChannelRead(dr) {\n\t\t\t\t\ts.markDialogRead(dr)\n\t\t\t\t}\n\t\t\t}\n\t\t\ts.logger.Printf(\"[dialog] injection injected=%v extra_len=%d\", injected, len(dr.Extra))\n\t\t}\n\t}\n\n\t// Signal tools are NOT injected into the main request.\n\t// They are collected via a separate async reflection call after the response.\n\t// See fireReflectionCall() in reflection.go.\n\t// NEVER put _signal_* tools back here — the model ignores them among 44+ real tools.\n\n\t// Legacy assistant annotation: timestamp on last assistant message after cache breakpoint.\n\t// User timestamps are now handled by TimestampStore (injected earlier in the pipeline).\n\tif threadID != \"\" {\n\t\tif msgs, ok := req[\"messages\"].([]any); ok && len(msgs) > 0 {\n\t\t\t// Find last cache_control breakpoint in messages\n\t\t\tcacheIdx := -1\n\t\t\tfor i, m := range msgs {\n\t\t\t\tmsg, _ := m.(map[string]any)\n\t\t\t\tif msg == nil {\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\t\t\t\tif _, has := msg[\"cache_control\"]; has {\n\t\t\t\t\tcacheIdx = i\n\t\t\t\t}\n\t\t\t\tif content, ok := msg[\"content\"].([]any); ok {\n\t\t\t\t\tfor _, block := range content {\n\t\t\t\t\t\tif b, ok := block.(map[string]any); ok {\n\t\t\t\t\t\t\tif _, has := b[\"cache_control\"]; has {\n\t\t\t\t\t\t\t\tcacheIdx = i\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t\tstartIdx := cacheIdx + 1\n\t\t\tif startIdx < 0 {\n\t\t\t\tstartIdx = 0\n\t\t\t}\n\t\t\ts.responseTsMu.RLock()\n\t\t\trespTime, hasRespTime := s.responseTimes[threadID]\n\t\t\ts.responseTsMu.RUnlock()\n\t\t\tif hasRespTime {\n\t\t\t\tfor i := len(msgs) - 1; i >= startIdx; i-- {\n\t\t\t\t\tmsg, _ := msgs[i].(map[string]any)\n\t\t\t\t\tif msg != nil && msg[\"role\"] == \"assistant\" {\n\t\t\t\t\t\tmeta := fmt.Sprintf(\"[%s] [msg:%d]\", respTime.Format(\"2006-01-02 15:04:05\"), i)\n\t\t\t\t\t\tprependMeta(msg, meta)\n\t\t\t\t\t\tneedsReserialization = true\n\t\t\t\t\t\tbreak\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\t// Self-Priming: DISABLED (experimental, see signal_handlers.go comment block).\n\t// The reflection-based selfprime (Haiku) was too inaccurate to be useful —\n\t// the main model adapts to user context organically from message history.\n\t// Kept for reference; re-enable by uncommenting below + setting ShouldActivate=true.\n\t//\n\t// s.selfPrimeMu.RLock()\n\t// anchor := s.selfPrimes[threadID]\n\t// s.selfPrimeMu.RUnlock()\n\t// if anchor != \"\" {\n\t// \tif msgs, ok := req[\"messages\"].([]any); ok && len(msgs) > 0 {\n\t// \t\tlastMsg, _ := msgs[len(msgs)-1].(map[string]any)\n\t// \t\tif lastMsg != nil && lastMsg[\"role\"] == \"user\" {\n\t// \t\t\tprimeBlock := map[string]any{\n\t// \t\t\t\t\"type\": \"text\",\n\t// \t\t\t\t\"text\": \"[self-prime]\\n[Self-Prime von deiner letzten Antwort]: \" + anchor,\n\t// \t\t\t}\n\t// \t\t\tif content, ok := lastMsg[\"content\"].([]any); ok {\n\t// \t\t\t\tlastMsg[\"content\"] = append(content, primeBlock)\n\t// \t\t\t} else if text, ok := lastMsg[\"content\"].(string); ok {\n\t// \t\t\t\tlastMsg[\"content\"] = []any{\n\t// \t\t\t\t\tmap[string]any{\"type\": \"text\", \"text\": text},\n\t// \t\t\t\t\tprimeBlock,\n\t// \t\t\t\t}\n\t// \t\t\t}\n\t// \t\t\tneedsReserialization = true\n\t// \t\t}\n\t// \t}\n\t// }\n\n\t// Prompt Caching: inject cache_control breakpoints on system + tools\n\t// Sawtooth: disabled (all 4 slots are used by CC system (2) + briefing (1) + CC messages (1))\n\t// Legacy: count existing breakpoints and fill up remaining slots.\n\tif !s.cfg.SawtoothEnabled && s.cacheGate.ShouldCache() {\n\t\tif n := InjectCacheBreakpoints(req, s.logger); n > 0 {\n\t\t\tneedsReserialization = true\n\t\t\ts.logger.Printf(\"[req %d %s] prompt cache: %d breakpoints injected\", reqIdx, proj, n)\n\t\t} else {\n\t\t\ts.logger.Printf(\"[req %d %s] prompt cache: no budget (4/4 already set)\", reqIdx, proj)\n\t\t}\n\t}\n\n\t// Final: upgrade cache TTL + enforce breakpoint limit (must run AFTER all injections + InjectCacheBreakpoints)\n\t// Shift the last messages-breakpoint from unstable user message to stable assistant message.\n\t// Text user messages change between turns (timestamps, reminders, structure); assistant messages don't.\n\t// Tool-result messages are excluded — they are stable cache anchors.\n\tif ShiftMessageBreakpoint(req) {\n\t\tneedsReserialization = true\n\t}\n\t// Normalize cache_control TTL across all blocks to prevent Anthropic from\n\t// rejecting requests with increasing TTLs in block order. Auto-detects 1h\n\t// when any existing block already carries it (config, Claude Code\n\t// passthrough on --resume, or stale frozen prefix from an earlier turn).\n\tif n := NormalizeCacheTTL(req, s.cfg.CacheTTL); n > 0 {\n\t\tneedsReserialization = true\n\t\ts.logger.Printf(\"[req %d %s] cache TTL normalized: %d blocks\", reqIdx, proj, n)\n\t}\n\tif n := EnforceCacheBreakpointLimit(req, maxCacheBreakpoints); n > 0 {\n\t\tneedsReserialization = true\n\t\ts.logger.Printf(\"[req %d %s] prompt cache: trimmed %d surplus breakpoints\", reqIdx, proj, n)\n\t}\n\n\t// Pre-flight: validate tool_use/tool_result pairing before forwarding.\n\t// Repairs orphans created by Stubify/Collapse/Injection pipeline.\n\tif msgs, ok := req[\"messages\"].([]any); ok {\n\t\trepaired, orphans := validateToolPairs(msgs, s.logger)\n\t\tif orphans > 0 {\n\t\t\treq[\"messages\"] = repaired\n\t\t\ts.logger.Printf(\"%s[req %d %s tid=%s] validate: repaired %d orphan tool_result(s)%s\", colorOrange, reqIdx, proj, threadID, orphans, colorReset)\n\t\t\tneedsReserialization = true\n\t\t}\n\t}\n\n\t// Early-out: skip re-serialization when nothing changed\n\tif !needsReserialization {\n\t\ts.logger.Printf(\"%s[req %d %s tid=%s] %d messages, ~%dk tokens (no stubbing, threshold=%dk)%s\", colorDim, reqIdx, proj, threadID, len(messages), totalTokens/1000, s.effectiveTokenThreshold(model)/1000, colorReset)\n\t\ts.forwardWithAnnotation(w, r, body, reqIdx, toolUseIDs, proj, threadID, msgCount, totalTokens)\n\t\treturn\n\t}\n\n\t// Re-serialize\n\tbody, err = json.Marshal(req)\n\tif err != nil {\n\t\ts.logger.Printf(\"%s[req %d %s tid=%s] re-marshal error: %v%s\", colorRed, reqIdx, proj, threadID, err, colorReset)\n\t\thttp.Error(w, \"internal proxy error\", http.StatusInternalServerError)\n\t\treturn\n\t}\n\n\t// Debug: dump final request body to file (enable with YESMEM_PROXY_DEBUG=1)\n\tif needsReserialization && os.Getenv(\"YESMEM_PROXY_DEBUG\") == \"1\" {\n\t\tdumpDir := filepath.Join(s.cfg.DataDir, \"debug\")\n\t\tos.MkdirAll(dumpDir, 0755)\n\t\tts := time.Now().Format(\"20060102-150405\")\n\t\tdumpPath := fmt.Sprintf(\"%s/req_%s_%d.json\", dumpDir, ts, reqIdx)\n\t\tos.WriteFile(dumpPath, body, 0644)\n\t\ts.logger.Printf(\"[req %d] dumped %dk to %s\", reqIdx, len(body)/1024, dumpPath)\n\t}\n\n\t// Task #7: Mark this request fingerprint for retry detection\n\tif !isRetryReq {\n\t\ts.markRequest(fp)\n\t}\n\n\t// Forward to Anthropic with SSE annotation extraction\n\t// Use original msgCount (pre-injection) so actual-based estimation stays consistent across turns\n\t// Use actual-based totalTokens as estimate when available (more accurate than full recount)\n\tfinalEstimate := totalTokens\n\tif s.sawtoothTrigger == nil || s.sawtoothTrigger.GetLastTokens(threadID) == 0 {\n\t\t// First request: no actual yet, do a full recount of post-injection messages\n\t\tfinalMsgs, _ := req[\"messages\"].([]any)\n\t\tfinalEstimate = s.measureOverhead(req) + s.countMessageTokens(finalMsgs)\n\t}\n\ts.logger.Printf(\"[req %d %s tid=%s] FINAL: ~%dk estimated (after all injections)\", reqIdx, proj, threadID, finalEstimate/1000)\n\tif raw := s.getRawEstimate(threadID); raw > 0 {\n\t\ts.cacheStatusWriter.UpdateRawForThread(threadID, raw)\n\t}\n\ts.forwardWithAnnotation(w, r, body, reqIdx, toolUseIDs, proj, threadID, msgCount, finalEstimate)\n}\n\n// getPromptFlags resolves the effective prompt flags for a profile, falling back\n// to the Server's legacy flat config if promptCfg is nil (backward compatibility).\nfunc (s *Server) getPromptFlags(profile models.PromptProfile) *config.PromptFlags {\n\tif s.promptCfg != nil {\n\t\treturn s.promptCfg.EffectivePromptFlags(profile)\n\t}\n\t// Legacy path: use flat fields as Claude defaults.\n\treturn &config.PromptFlags{\n\t\tUngate: s.cfg.PromptUngate,\n\t\tRewrite: s.cfg.PromptRewrite,\n\t\tEnhance: s.cfg.PromptEnhance,\n\t\tToolPrefs: s.cfg.PromptToolPrefs,\n\t\tOutputDiscipline: s.cfg.PromptOutputDiscipline,\n\t\tCodingDiscipline: s.cfg.PromptCodingDiscipline,\n\t\tBeweislast: s.cfg.PromptBeweislast,\n\t\tFable: s.cfg.PromptFable,\n\t\tScopeDiscipline: s.cfg.PromptScopeDiscipline,\n\t\tDelegationContract: s.cfg.PromptDelegationContract,\n\t\tClarifyFirst: s.cfg.PromptClarifyFirst,\n\t\tCodeToolsFirst: s.cfg.PromptCodeToolsFirst,\n\t\tWikiFirst: s.cfg.PromptWikiFirst,\n\t\tPatternSuggest: s.cfg.PromptPatternSuggest,\n\t}\n}\n\n// getThreadCWD returns the cached working directory for a thread.\nfunc (s *Server) getThreadCWD(threadID string) string {\n\ts.threadCWDMu.RLock()\n\tdefer s.threadCWDMu.RUnlock()\n\tif s.threadCWD == nil {\n\t\treturn \"\"\n\t}\n\treturn s.threadCWD[threadID]\n}\n\n// setThreadCWD caches the working directory for a thread.\nfunc (s *Server) setThreadCWD(threadID, cwd string) {\n\ts.threadCWDMu.Lock()\n\tdefer s.threadCWDMu.Unlock()\n\tif s.threadCWD == nil {\n\t\ts.threadCWD = make(map[string]string)\n\t}\n\ts.threadCWD[threadID] = cwd\n}\n\n// resolveOpenAITarget returns the upstream URL for an OpenAI-format request.\n// Checks ProviderTargets by prefix match (e.g. \"deepseek\" matches \"deepseek-v4-pro\"),\n// then by exact model name, then falls back to OpenAITargetURL.\nfunc (s *Server) resolveOpenAITarget(model string) string {\n\tmodelLower := strings.ToLower(model)\n\tif len(s.cfg.ProviderTargets) > 0 {\n\t\tfor key, url := range s.cfg.ProviderTargets {\n\t\t\tif url == \"\" {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tkeyLower := strings.ToLower(key)\n\t\t\tif strings.HasPrefix(modelLower, keyLower) || strings.HasPrefix(modelLower, keyLower+\"-\") {\n\t\t\t\treturn strings.TrimRight(url, \"/\")\n\t\t\t}\n\t\t}\n\t\tfor key, url := range s.cfg.ProviderTargets {\n\t\t\tif url != \"\" && strings.EqualFold(key, model) {\n\t\t\t\treturn strings.TrimRight(url, \"/\")\n\t\t\t}\n\t\t}\n\t}\n\t// Auto-discovered targets (secondary — explicit provider_targets takes priority)\n\tif len(s.autoProviderTargets) > 0 {\n\t\tif url, ok := s.autoProviderTargets[modelLower]; ok && url != \"\" {\n\t\t\treturn url\n\t\t}\n\t}\n\n\tif s.cfg.OpenAITargetURL != \"\" {\n\t\treturn s.cfg.OpenAITargetURL\n\t}\n\treturn s.cfg.TargetURL\n}\n\n// resolveAnthropicTarget returns the upstream URL for an Anthropic-format request.\n// Checks ProviderTargets by prefix match (e.g. \"deepseek\" matches \"deepseek-v4-pro\"),\n// then by exact model name, then falls back to TargetURL.\nfunc (s *Server) resolveAnthropicTarget(model string) string {\n\tmodelLower := strings.ToLower(model)\n\tif len(s.cfg.ProviderTargets) > 0 {\n\t\tfor key, url := range s.cfg.ProviderTargets {\n\t\t\tif url == \"\" {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tkeyLower := strings.ToLower(key)\n\t\t\tif strings.HasPrefix(modelLower, keyLower) || strings.HasPrefix(modelLower, keyLower+\"-\") {\n\t\t\t\treturn strings.TrimRight(url, \"/\")\n\t\t\t}\n\t\t}\n\t\tfor key, url := range s.cfg.ProviderTargets {\n\t\t\tif url != \"\" && strings.EqualFold(key, model) {\n\t\t\t\treturn strings.TrimRight(url, \"/\")\n\t\t\t}\n\t\t}\n\t}\n\treturn s.cfg.TargetURL\n}\n\n// bytesPerToken is the approximation factor for token estimation.\n// Anthropic recommends ~3.5 chars/token; we use 3.6 for slight overestimation\n// (triggers stubbing a bit earlier = safer for context budget).\nconst bytesPerToken = 3.6\n\n// TokenEstimateFunc is the central token estimation function.\n// All stubbing/budget decisions use this. Receives extracted text content\n// (not JSON wire format) so the tokenizer counts actual content tokens.\ntype TokenEstimateFunc func(text string) int\n"}, "4d45523ad68e5a5aa32321070e0302a07ce59643": {"t": 1785759346.052999, "n": 76017, "h": "b62ed2a1d2a3f5fe", "c": "package proxy\n\nimport (\n\t\"context\"\n\t\"crypto/tls\"\n\t\"encoding/json\"\n\t\"fmt\"\n\t\"io\"\n\t\"log\"\n\t\"net\"\n\t\"net/http\"\n\t\"os\"\n\t\"os/signal\"\n\t\"path/filepath\"\n\t\"strconv\"\n\t\"strings\"\n\t\"sync\"\n\t\"sync/atomic\"\n\t\"syscall\"\n\t\"time\"\n\n\t\"github.com/carsteneu/yesmem/internal/buildinfo\"\n\t\"github.com/carsteneu/yesmem/internal/config\"\n\t\"github.com/carsteneu/yesmem/internal/models\"\n\ttokenizer \"github.com/qhenkart/anthropic-tokenizer-go\"\n)\n\n// Config holds proxy configuration.\ntype Config struct {\n\tListenAddr string // e.g. \":9099\"\n\tTargetURL string // e.g. \"https://api.anthropic.com\"\n\tTokenThreshold int // trigger stubbing above this estimated token count\n\tTokenMinimumThreshold int // stub down to this floor\n\tTokenThresholds map[string]int // model-specific thresholds: {\"opus\": 180000, \"haiku\": 130000}\n\tKeepRecent int // number of messages to always keep unmodified\n\tDataDir string // yesmem data directory for DB access\n\tOpenAITargetURL string // upstream for OpenAI-format clients; if empty, uses TargetURL\n\tProviderTargets map[string]string // per-provider upstream URLs, e.g. {\"deepseek\": \"https://api.deepseek.com\"}\n\tAutoConfigureProviders bool // enable auto-discovery of provider targets from opencode config\n\n\t// Signal reflection\n\tSignalsEnabled bool // enable async signal reflection calls\n\tSignalsMode string // \"reflection\" = separate API call\n\tSignalsEveryNTurns int // every N end_turn responses\n\tSignalsModel string // full model ID, e.g. \"claude-sonnet-4-6\"\n\tAPIKey string // needed for reflection API calls\n\n\t// Sawtooth cache optimization\n\tSawtoothEnabled bool // use sawtooth instead of progressive decay (default: true)\n\tCacheTTL string // \"ephemeral\" (5m) or \"1h\" (extended)\n\n\t// Usage deflation: scale down input_tokens reported to CC to suppress \"Context low\" warning.\n\t// 0 = disabled, 0.7 = report 70% of actual tokens. Real values kept for internal tracking.\n\tUsageDeflationFactor float64\n\n\t// System prompt rewriting\n\tPromptUngate bool // strip \"may or may not be relevant\" disclaimer from CLAUDE.md injection\n\tPromptRewrite bool // strip \"Output efficiency\" + \"short and concise\", inject Ant-quality directives\n\tPromptEnhance bool // CLAUDE.md authority reinforcement, comment discipline, persona-based tone\n\tPromptToolPrefs bool // inject [yesmem-tool-prefs] Edit/Write preference + error-semantics warning\n\tPromptOutputDiscipline bool // inject [yesmem-output-discipline] no-preamble + no-skill-eval + exploratory-heuristic\n\tPromptCodingDiscipline bool // inject [yesmem-coding-discipline] read-before-propose + no-brute-force + no-half-finished\n\tPromptBeweislast bool // inject [yesmem-beweislast] fabrication-guard + claim-vs-proof + stance-under-challenge + tool-result-honesty + long-context-erosion\n\tPromptScopeDiscipline bool // inject [yesmem-scope-discipline] deliver-A-not-A+B+C + adjacent-findings-separate + scope-bound-authorization\n\tPromptDelegationContract bool // inject [yesmem-delegation-contract] self-contained-prompts + parallel-dispatch\n\tPromptClarifyFirst bool // inject [yesmem-clarify-first] clarify only when alternative interpretations produce materially different work\n\tPromptCodeToolsFirst bool // inject [yesmem-code-tools-first] prefer MCP code-navigation tools over Agent spawns\n\tPromptWikiFirst bool // inject [yesmem-wiki-first] check per-file wiki BEFORE editing\n\tPromptPatternSuggest bool // record repeated shell-command shapes for offline cap-suggestion analysis\n\tEffortFloor string // minimum effort level: \"low\", \"medium\", \"high\", \"max\" (empty = off)\n\tSkillEvalInject string // \"true\" = verbose eval, \"silent\" = internal eval only, \"false\" = disabled\n\n\t// Cache keepalive\n\tCacheKeepaliveEnabled bool\n\tCacheKeepaliveMode string // \"auto\", \"5m\", \"1h\"\n\tCacheKeepalivePings5m int\n\tCacheKeepalivePings1h int\n\tCacheKeepaliveMinMessages int // skip keepalive when request body has fewer messages (0 = always)\n\n\t// Forked agents\n\tForkedAgentsEnabled bool\n\tForkedAgentsModel string // full model ID\n\tForkedAgentsTokenGrowthTrigger int\n\tForkedAgentsMaxFailures int\n\tForkedAgentsMaxForksPerSession int\n\tForkedAgentsDebug bool\n\n\t// Quality model fallback for forked agents\n\tQualityModelID string\n\n\t// Cache state management\n\tResetCache bool // clear persisted frozen stubs and decay state on startup\n\n\t// ModelFeatures: per-model behavioral feature gates (prefix-matched).\n\t// Falls back to FeatureDefaults for models not listed.\n\tModelFeatures map[string]*config.FeatureGates `yaml:\"model_features\"`\n\tFeatureDefaults *config.FeatureGates `yaml:\"feature_defaults\"`\n\n\t// Custom system prompt: shared SYSTEM.md template for OpenCode and Claude Code pipelines.\n\tCustomSystemPrompt CustomSystemPromptConfig\n}\n\n// CustomSystemPromptConfig controls which proxy pipelines use the SYSTEM.md template.\ntype CustomSystemPromptConfig struct {\n\tEnabledOpenCode bool\n\tEnabledClaudeCode bool\n\tEnabledCodex bool\n\tTemplatePath string\n}\n\nconst maxAnnotations = 5000 // evict oldest when exceeded\n\n// ANSI color codes for log output\nconst (\n\tcolorReset = \"\\033[0m\"\n\tcolorDim = \"\\033[2m\" // gray — passthrough / no stubbing\n\tcolorGreen = \"\\033[32m\" // stubbed / compressed\n\tcolorLightGreen = \"\\033[92m\" // FINAL without compression\n\tcolorBlue = \"\\033[34m\" // injection\n\tcolorOrange = \"\\033[33m\" // skip / retry\n\tcolorYellow = \"\\033[33m\" // warnings\n\tcolorRed = \"\\033[31m\" // errors\n)\n\n// Server is the infinite-thread proxy.\ntype Server struct {\n\tcfg Config\n\tautoProviderTargets map[string]string // auto-discovered modelID → upstreamURL\n\thttpClient *http.Client\n\tlogger *log.Logger\n\tversion string // build version for log tracing\n\trequestIdx atomic.Int64\n\n\t// Post-hoc annotations: tool_use_id → first ~120 chars of Claude's response\n\tmu sync.RWMutex\n\tannotations map[string]string\n\n\t// Progressive decay tracker\n\tdecay *DecayTracker\n\n\t// Living narrative block\n\tnarrative *Narrative\n\n\t// Pivot moments cache (from daemon)\n\tpivotMu sync.RWMutex\n\tpivotTexts []string\n\tpivotCached time.Time\n\n\t// Aggregate stats (Task #2)\n\tstats *ProxyStats\n\n\t// Local tokenizer for accurate token counting\n\ttokenizer *tokenizer.Tokenizer\n\n\t// Cached overhead tokens (system + tools), set on first request per thread\n\toverheadMu sync.RWMutex\n\toverheadTokens int // unused, kept for interface compat\n\n\t// Hysteresis state for stubbing (Task #6)\n\tstubActive atomic.Bool\n\n\t// Daemon connection state — first successful connect flips to true\n\tdaemonReady atomic.Bool\n\n\t// Retry detection (Task #7)\n\tretryMu sync.RWMutex\n\tlastFingerprint string\n\n\t// Session start time for temporal annotation in archive blocks\n\tsessionStartMu sync.RWMutex\n\tsessionStartTime time.Time\n\n\t// Runtime config override (set via MCP set_config tool)\n\tconfigOverrideMu sync.RWMutex\n\ttokenThresholdOverrides map[string]int // model-key → threshold, \"\" = global fallback\n\n\t// Prompt rewrite miss logging: function+Claude Code version → last log time\n\trewriteMissMu sync.Mutex\n\trewriteMissLog map[string]time.Time\n\n\t// Briefing cache — keyed by threadID. Each Claude Code session thread gets\n\t// its own briefing+codemap snapshot so a sawtooth refreeze on thread A\n\t// cannot invalidate thread B's cached message-prefix hash. The cache entry\n\t// remembers which project it was loaded for; switching project for the\n\t// same thread evicts the old entry on next read.\n\tbriefingMu sync.RWMutex\n\tbriefingCache map[string]briefingEntry\n\n\t// briefingLoader is an optional test-only seam for refreshBriefing.\n\t// Nil in production → refreshBriefing falls back to s.loadBriefing.\n\tbriefingLoader func(project, projectDir string) briefingData\n\n\t// Cognitive signal bus — routes _signal_* tool calls to handlers\n\tsignalBus *SignalBus\n\n\t// Inline reflection: track which learning IDs were injected per thread\n\tlastInjectedIDsMu sync.Mutex\n\tlastInjectedIDs map[string]map[int64]string // threadID → id → source (\"briefing\"|\"associative\"|\"fresh\")\n\tsessionInjectCounts map[string]map[int64]int // threadID → learningID → injection count this session\n\tlastTurnInjected map[string]map[int64]bool // threadID → learningIDs injected in previous turn\n\n\t// Self-priming anchors: threadID → last self-prime text\n\tselfPrimeMu sync.RWMutex\n\tselfPrimes map[string]string\n\n\t// Timestamp tracking: threadID → time of last response completion\n\tresponseTsMu sync.RWMutex\n\tresponseTimes map[string]time.Time\n\n\t// Think reminder: per-thread request counter (replaces hook-think file-based counter)\n\tthinkMu sync.Mutex\n\tthinkCounters map[string]int // threadID → request count\n\n\tchannelMu sync.Mutex\n\tchannelInjectCount map[string]int // sessionID → injection turn count\n\n\t// Prompt cache gating — enables cache_control breakpoints when requests are frequent\n\tcacheGate *CacheGate\n\n\t// Sawtooth cache optimization\n\tfrozenStubs *FrozenStubs\n\teagerStubMemory *EagerStubMemory\n\tcapsCache *CapsCache\n\tsawtoothTrigger *SawtoothTrigger\n\ttimestampStore *TimestampStore\n\tcacheStatusWriter *CacheStatusWriter\n\tcacheKeepalive *CacheKeepalive\n\tcacheTTLDetector *CacheTTLDetector\n\t// promptCfg is the profile-aware prompt configuration from config.ProxyConfig.\n\t// Used to resolve EffectivePromptFlags(profile) for multi-agent prompt isolation.\n\tpromptCfg *config.ProxyConfig\n\t// threadCWD caches the working directory per thread (for opencode: only in first message)\n\tthreadCWDMu sync.RWMutex\n\tthreadCWD map[string]string // threadID → cwd\n\n\t// Cumulative token savings from collapsing (per threadID)\n\trawSavingsMu sync.Mutex\n\trawSavings map[string]int\n\n\t// Skill hint tracking: per-thread active skills\n\tskillTracker *skillHintTracker\n\n\t// Forked agent state: per-thread token growth + failure tracking\n\tforkState *ForkState\n\tforkConfigs []ForkConfig\n\n\t// Custom system prompt template for OpenCode (loaded from DataDir/SYSTEM.md)\n\tcustomSystemPrompt []byte\n\n\t// Rules re-injection: condensed CLAUDE.md rules injected every ~40k tokens\n\trulesMu sync.RWMutex\n\trulesBlock string // cached condensed rules (fetched once from daemon)\n\trulesTokenCount map[string]int // threadID → tokens since last rules injection\n\trulesCollapseInjected map[string]bool // threadID → true if collapse already injected rules (reset by normal inject)\n\tmsgCounters *msgCounters // global per-thread msg counter, persists across collapses\n\n\t// Loop detection: per-thread warning state\n\tloopMu sync.Mutex\n\tloopStates map[string]*LoopState // threadID → state\n\n\t// Injection overhead: per-thread delta between API-actual and local BPE estimate\n\tinjectionOverheadMu sync.RWMutex\n\tinjectionOverhead map[string]int // threadID → overhead tokens\n}\n\n// Run starts the proxy server and blocks until interrupted.\nfunc Run(cfg Config) error {\n\ts := &Server{\n\t\tcfg: cfg,\n\t\thttpClient: &http.Client{\n\t\t\tTimeout: 10 * time.Minute,\n\t\t\tTransport: &http.Transport{\n\t\t\t\tTLSClientConfig: &tls.Config{},\n\t\t\t\tMaxIdleConns: 10,\n\t\t\t\tIdleConnTimeout: 90 * time.Second,\n\t\t\t\tDisableCompression: true,\n\t\t\t\tResponseHeaderTimeout: 60 * time.Second,\n\t\t\t},\n\t\t},\n\t\tlogger: createLogger(cfg.DataDir),\n\t\tannotations: make(map[string]string),\n\t\tdecay: NewDecayTracker(),\n\t\tnarrative: NewNarrative(),\n\t\tstats: &ProxyStats{startTime: time.Now()},\n\t\tselfPrimes: make(map[string]string),\n\t\tlastInjectedIDs: make(map[string]map[int64]string),\n\t\tversion: buildinfo.Version,\n\t\tsessionInjectCounts: make(map[string]map[int64]int),\n\t\tlastTurnInjected: make(map[string]map[int64]bool),\n\t\tresponseTimes: make(map[string]time.Time),\n\t\tthinkCounters: make(map[string]int),\n\t\tchannelInjectCount: make(map[string]int),\n\t\trewriteMissLog: make(map[string]time.Time),\n\t\tcacheGate: NewCacheGate(cacheGapForTTL(cfg.CacheTTL)),\n\t\tfrozenStubs: NewFrozenStubsWithTTL(sawtoothTTLForCacheTTL(cfg.CacheTTL)),\n\t\teagerStubMemory: NewEagerStubMemory(),\n\t\tcapsCache: NewCapsCache(),\n\t\ttimestampStore: NewTimestampStore(),\n\t\tsawtoothTrigger: NewSawtoothTrigger(cacheGapForTTL(cfg.CacheTTL), cfg.TokenThreshold, cfg.TokenMinimumThreshold),\n\t\tcacheStatusWriter: NewCacheStatusWriter(cfg.DataDir, cfg.CacheTTL, cfg.TokenThreshold, cfg.TokenMinimumThreshold),\n\t\tcacheTTLDetector: NewCacheTTLDetectorWithPersist(cfg.DataDir),\n\t\tskillTracker: newSkillHintTracker(),\n\t\trulesTokenCount: make(map[string]int),\n\t\trulesCollapseInjected: make(map[string]bool),\n\t\tmsgCounters: newMsgCounters(),\n\t\tloopStates: make(map[string]*LoopState),\n\t\tinjectionOverhead: make(map[string]int),\n\t\tforkState: NewForkState(cfg.ForkedAgentsTokenGrowthTrigger, 60000, cfg.ForkedAgentsMaxFailures, cfg.ForkedAgentsMaxForksPerSession),\n\t\tforkConfigs: []ForkConfig{},\n\t}\n\n\t// Load custom system prompt from configured path\n\tif cfg.CustomSystemPrompt.TemplatePath != \"\" &&\n\t\t(cfg.CustomSystemPrompt.EnabledOpenCode || cfg.CustomSystemPrompt.EnabledClaudeCode || cfg.CustomSystemPrompt.EnabledCodex) {\n\t\ts.customSystemPrompt = loadSystemPromptFromPath(cfg.CustomSystemPrompt.TemplatePath)\n\t\tif s.customSystemPrompt != nil {\n\t\t\ts.logger.Printf(\"[proxy] loaded custom system prompt from %s (%d bytes, opencode=%v cc=%v codex=%v)\",\n\t\t\t\tcfg.CustomSystemPrompt.TemplatePath, len(s.customSystemPrompt),\n\t\t\t\tcfg.CustomSystemPrompt.EnabledOpenCode, cfg.CustomSystemPrompt.EnabledClaudeCode, cfg.CustomSystemPrompt.EnabledCodex)\n\t\t} else {\n\t\t\ts.logger.Printf(\"[proxy] WARNING: custom_system_prompt enabled but template not found at %q — feature disabled\",\n\t\t\t\tcfg.CustomSystemPrompt.TemplatePath)\n\t\t}\n\t}\n\n\t// Log persisted detection state\n\tif sup := s.cacheTTLDetector.Is1hSupported(); sup != nil {\n\t\ts.logger.Printf(\"[proxy] loaded persisted TTL detection: 1h_supported=%v\", *sup)\n\t\tif *sup && s.frozenStubs != nil {\n\t\t\ts.frozenStubs.UpdateTTL(sawtoothTTLForCacheTTL(\"1h\"))\n\t\t}\n\t}\n\n\tif cfg.CacheKeepaliveEnabled {\n\t\ts.cacheKeepalive = NewCacheKeepalive(CacheKeepaliveConfig{\n\t\t\tTarget: cfg.TargetURL,\n\t\t\tProviderTargets: cfg.ProviderTargets,\n\t\t\tMode: cfg.CacheKeepaliveMode,\n\t\t\tPings5m: cfg.CacheKeepalivePings5m,\n\t\t\tPings1h: cfg.CacheKeepalivePings1h,\n\t\t\tMinMessages: cfg.CacheKeepaliveMinMessages,\n\t\t\tDetector: s.cacheTTLDetector,\n\t\t\tLogger: s.logger,\n\t\t\tOnPing: func(threadID string, cacheRead, cacheWrite int) {\n\t\t\t\ts.cacheTTLDetector.RecordResponse(cacheRead, cacheWrite, 0)\n\t\t\t\tif s.frozenStubs != nil {\n\t\t\t\t\ts.frozenStubs.Touch(threadID)\n\t\t\t\t}\n\t\t\t\tif s.sawtoothTrigger != nil {\n\t\t\t\t\ts.sawtoothTrigger.TouchRequestTime(threadID)\n\t\t\t\t}\n\t\t\t},\n\t\t})\n\t}\n\n\t// Wire detector + keepalive to status writer for terminal display\n\ts.cacheStatusWriter.SetDetector(s.cacheTTLDetector)\n\tif s.cacheKeepalive != nil {\n\t\ts.cacheStatusWriter.SetKeepalive(s.cacheKeepalive)\n\t}\n\n\t// Register forked agent configs\n\tif cfg.ForkedAgentsEnabled {\n\t\tmodel := cfg.ForkedAgentsModel\n\t\ts.forkConfigs = append(s.forkConfigs, NewExtractAndEvaluateConfig(model))\n\t\tmodelDesc := model\n\t\tif modelDesc == \"\" {\n\t\t\tmodelDesc = \"(same as main thread)\"\n\t\t}\n\t\ts.logger.Printf(\"[proxy] forked agents enabled: %d configs, model=%s, trigger=%dk tokens\",\n\t\t\tlen(s.forkConfigs), modelDesc, cfg.ForkedAgentsTokenGrowthTrigger/1000)\n\t}\n\n\t// Wire sawtooth token persistence via daemon RPC\n\ts.sawtoothTrigger.SetPersistFunc(func(key, value string) {\n\t\tif _, err := s.queryDaemon(\"set_proxy_state\", map[string]any{\"key\": key, \"value\": value}); err != nil {\n\t\t\ts.logger.Printf(\"[sawtooth] persist failed for %s: %v\", key, err)\n\t\t}\n\t})\n\ts.sawtoothTrigger.SetLoadFunc(func(key string) (string, bool) {\n\t\tresult, err := s.queryDaemon(\"get_proxy_state\", map[string]any{\"key\": key})\n\t\tif err != nil || result == nil {\n\t\t\treturn \"\", false\n\t\t}\n\t\tvar resp struct {\n\t\t\tValue string `json:\"value\"`\n\t\t}\n\t\tif json.Unmarshal(result, &resp) != nil || resp.Value == \"\" {\n\t\t\treturn \"\", false\n\t\t}\n\t\treturn resp.Value, true\n\t})\n\n\t// Wire frozen stubs persistence via same daemon RPC\n\ts.frozenStubs.SetPersistFunc(func(key, value string) {\n\t\tif _, err := s.queryDaemon(\"set_proxy_state\", map[string]any{\"key\": key, \"value\": value}); err != nil {\n\t\t\ts.logger.Printf(\"[frozen] persist failed for %s: %v\", key, err)\n\t\t}\n\t})\n\ts.frozenStubs.SetLoadFunc(func(key string) (string, bool) {\n\t\tresult, err := s.queryDaemon(\"get_proxy_state\", map[string]any{\"key\": key})\n\t\tif err != nil || result == nil {\n\t\t\treturn \"\", false\n\t\t}\n\t\tvar resp struct {\n\t\t\tValue string `json:\"value\"`\n\t\t}\n\t\tif json.Unmarshal(result, &resp) != nil || resp.Value == \"\" {\n\t\t\treturn \"\", false\n\t\t}\n\t\treturn resp.Value, true\n\t})\n\n\t// Wire eager-stub memory persistence via same daemon RPC.\n\t// Persists per-thread tool_use_id sets so stub decisions survive deploys.\n\ts.eagerStubMemory.SetPersistFunc(func(key, value string) {\n\t\tif _, err := s.queryDaemon(\"set_proxy_state\", map[string]any{\"key\": key, \"value\": value}); err != nil {\n\t\t\ts.logger.Printf(\"[eagerstub] persist failed for %s: %v\", key, err)\n\t\t}\n\t})\n\ts.eagerStubMemory.SetLoadFunc(func(key string) (string, bool) {\n\t\tresult, err := s.queryDaemon(\"get_proxy_state\", map[string]any{\"key\": key})\n\t\tif err != nil || result == nil {\n\t\t\treturn \"\", false\n\t\t}\n\t\tvar resp struct {\n\t\t\tValue string `json:\"value\"`\n\t\t}\n\t\tif json.Unmarshal(result, &resp) != nil || resp.Value == \"\" {\n\t\t\treturn \"\", false\n\t\t}\n\t\treturn resp.Value, true\n\t})\n\n\t// Wire decay tracker persistence via same daemon RPC\n\ts.decay.SetPersistFunc(func(key, value string) {\n\t\tif _, err := s.queryDaemon(\"set_proxy_state\", map[string]any{\"key\": key, \"value\": value}); err != nil {\n\t\t\ts.logger.Printf(\"[decay] persist failed for %s: %v\", key, err)\n\t\t}\n\t})\n\ts.decay.SetLoadFunc(func(key string) (string, bool) {\n\t\tresult, err := s.queryDaemon(\"get_proxy_state\", map[string]any{\"key\": key})\n\t\tif err != nil || result == nil {\n\t\t\treturn \"\", false\n\t\t}\n\t\tvar resp struct {\n\t\t\tValue string `json:\"value\"`\n\t\t}\n\t\tif json.Unmarshal(result, &resp) != nil || resp.Value == \"\" {\n\t\t\treturn \"\", false\n\t\t}\n\t\treturn resp.Value, true\n\t})\n\n\t// Wire timestamp store persistence via same daemon RPC\n\ts.timestampStore.SetPersistFunc(func(key, value string) {\n\t\tif _, err := s.queryDaemon(\"set_proxy_state\", map[string]any{\"key\": key, \"value\": value}); err != nil {\n\t\t\ts.logger.Printf(\"[timestamps] persist failed for %s: %v\", key, err)\n\t\t}\n\t})\n\ts.timestampStore.SetLoadFunc(func(key string) (string, bool) {\n\t\tresult, err := s.queryDaemon(\"get_proxy_state\", map[string]any{\"key\": key})\n\t\tif err != nil || result == nil {\n\t\t\treturn \"\", false\n\t\t}\n\t\tvar resp struct {\n\t\t\tValue string `json:\"value\"`\n\t\t}\n\t\tif json.Unmarshal(result, &resp) != nil || resp.Value == \"\" {\n\t\t\treturn \"\", false\n\t\t}\n\t\treturn resp.Value, true\n\t})\n\n\t// Clear persisted cache state if --reset-cache was passed\n\tif cfg.ResetCache {\n\t\tfor _, prefix := range []string{\"frozen:\", \"decay:\", \"timestamps:\"} {\n\t\t\tresult, err := s.queryDaemon(\"delete_proxy_state_prefix\", map[string]any{\"prefix\": prefix})\n\t\t\tif err != nil {\n\t\t\t\ts.logger.Printf(\"[reset-cache] failed to delete %s*: %v\", prefix, err)\n\t\t\t} else {\n\t\t\t\tvar resp struct{ Deleted int }\n\t\t\t\tjson.Unmarshal(result, &resp)\n\t\t\t\ts.logger.Printf(\"[reset-cache] deleted %d %s* entries\", resp.Deleted, prefix)\n\t\t\t}\n\t\t}\n\t}\n\n\t// Init signal bus with daemon RPC as callback\n\ts.signalBus = NewSignalBus(s.logger)\n\tregisterSignalHandlers(s.signalBus, s.logger, s.queryDaemon, func(threadID, anchor string) {\n\t\ts.selfPrimeMu.Lock()\n\t\ts.selfPrimes[threadID] = anchor\n\t\ts.selfPrimeMu.Unlock()\n\t})\n\n\t// Init local tokenizer (BPE-based, ~60ms)\n\ttok, err := tokenizer.New()\n\tif err != nil {\n\t\ts.logger.Printf(\"%s[proxy] tokenizer init failed, falling back to heuristic: %v%s\", colorOrange, err, colorReset)\n\t} else {\n\t\ts.tokenizer = tok\n\t\ts.logger.Printf(\"[proxy] tokenizer initialized\")\n\t}\n\n\t// Auto-discover provider targets from opencode config\n\tif cfg.AutoConfigureProviders {\n\t\ts.autoProviderTargets = runAutoDiscovery(s.logger)\n\t} else {\n\t\ts.logger.Printf(\"[proxy] auto_configure_providers is disabled — skipping auto-discovery\")\n\t}\n\n\tmux := http.NewServeMux()\n\tmux.HandleFunc(\"/health\", s.handleHealth)\n\tmux.HandleFunc(\"/\", s.handleRequest)\n\n\tsrv := &http.Server{\n\t\tAddr: sanitizeListenAddr(cfg.ListenAddr),\n\t\tHandler: mux,\n\t}\n\n\tdone := make(chan os.Signal, 1)\n\tsignal.Notify(done, syscall.SIGINT, syscall.SIGTERM)\n\n\tgo func() {\n\t\t<-done\n\t\ts.logger.Println(\"shutting down (draining in-flight requests)...\")\n\t\tif s.cacheKeepalive != nil {\n\t\t\ts.cacheKeepalive.Stop()\n\t\t}\n\t\tctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)\n\t\tdefer cancel()\n\t\tif err := srv.Shutdown(ctx); err != nil {\n\t\t\ts.logger.Printf(\"graceful shutdown failed: %v, forcing close\", err)\n\t\t\tsrv.Close()\n\t\t}\n\t}()\n\n\ts.logger.Printf(\"listening on %s → %s (threshold=%d, keepRecent=%d, model_thresholds=%v)\",\n\t\tsanitizeListenAddr(cfg.ListenAddr), cfg.TargetURL, cfg.TokenThreshold, cfg.KeepRecent, cfg.TokenThresholds)\n\n\tif err := srv.ListenAndServe(); err != http.ErrServerClosed {\n\t\treturn fmt.Errorf(\"listen: %w\", err)\n\t}\n\treturn nil\n}\n\n// effectiveTokenThreshold returns the runtime token threshold for a given model.\n// Priority: session override (via set_config with session_id) > model-specific override (MCP set_config token_threshold:)\n// > model-specific override (config TokenThresholds) > global override (set_config token_threshold) > config global default.\nfunc (s *Server) effectiveTokenThreshold(model string) int {\n\ts.configOverrideMu.RLock()\n\toverrides := s.tokenThresholdOverrides\n\ts.configOverrideMu.RUnlock()\n\t// 1. MCP override (model-specific)\n\tif len(overrides) > 0 && model != \"\" {\n\t\tlower := strings.ToLower(model)\n\t\tfor key, threshold := range overrides {\n\t\t\tif key != \"\" && strings.Contains(lower, strings.ToLower(key)) && threshold > 0 {\n\t\t\t\treturn threshold\n\t\t\t}\n\t\t}\n\t}\n\t// 2. Config model-specific\n\tif model != \"\" && len(s.cfg.TokenThresholds) > 0 {\n\t\tlower := strings.ToLower(model)\n\t\tfor key, threshold := range s.cfg.TokenThresholds {\n\t\t\tif strings.Contains(lower, strings.ToLower(key)) {\n\t\t\t\treturn threshold\n\t\t\t}\n\t\t}\n\t}\n\t// 3. Global override fallback (key \"\")\n\tif len(overrides) > 0 {\n\t\tif globalOverride, ok := overrides[\"\"]; ok && globalOverride > 0 {\n\t\t\treturn globalOverride\n\t\t}\n\t}\n\t// 4. Config global default\n\treturn s.cfg.TokenThreshold\n}\n\n// SetTokenThresholdOverride sets a runtime override for the token threshold per model key.\n// Pass threshold=0 to reset the override for that key. Key \"\" is the global fallback.\nfunc (s *Server) SetTokenThresholdOverride(modelKey string, threshold int) {\n\ts.configOverrideMu.Lock()\n\tif s.tokenThresholdOverrides == nil {\n\t\ts.tokenThresholdOverrides = make(map[string]int)\n\t}\n\tif threshold <= 0 {\n\t\tdelete(s.tokenThresholdOverrides, modelKey)\n\t} else {\n\t\ts.tokenThresholdOverrides[modelKey] = threshold\n\t}\n\ts.configOverrideMu.Unlock()\n\n\t// Propagate global threshold to sawtooth trigger so runtime overrides take effect\n\tif modelKey == \"\" && s.sawtoothTrigger != nil {\n\t\tif threshold > 0 {\n\t\t\ts.sawtoothTrigger.SetTokenThreshold(threshold)\n\t\t} else {\n\t\t\ts.sawtoothTrigger.SetTokenThreshold(s.cfg.TokenThreshold)\n\t\t}\n\t}\n}\n\n// refreshConfigOverrides loads runtime config overrides from daemon proxy_state.\n// Checks in priority order: session-specific, model-specific, then global.\nfunc (s *Server) refreshConfigOverrides(threadID string) {\n\t// 1. Session-specific override (highest priority)\n\tif threadID != \"\" {\n\t\tresult, err := s.queryDaemon(\"get_proxy_state\", map[string]any{\"key\": \"config_override:token_threshold:\" + threadID})\n\t\tif err == nil && result != nil {\n\t\t\tvar resp struct {\n\t\t\t\tValue string `json:\"value\"`\n\t\t\t}\n\t\t\tif json.Unmarshal(result, &resp) == nil && resp.Value != \"\" {\n\t\t\t\tif v, err := strconv.Atoi(resp.Value); err == nil && v > 0 {\n\t\t\t\t\ts.SetTokenThresholdOverride(\"\", v)\n\t\t\t\t\treturn\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\t// 2. Model-specific overrides (e.g. config_override:token_threshold:deepseek)\n\tfor modelKey := range s.cfg.TokenThresholds {\n\t\tresult, err := s.queryDaemon(\"get_proxy_state\", map[string]any{\"key\": \"config_override:token_threshold:\" + modelKey})\n\t\tif err == nil && result != nil {\n\t\t\tvar resp struct {\n\t\t\t\tValue string `json:\"value\"`\n\t\t\t}\n\t\t\tif json.Unmarshal(result, &resp) == nil && resp.Value != \"\" {\n\t\t\t\tif v, err := strconv.Atoi(resp.Value); err == nil && v > 0 {\n\t\t\t\t\ts.SetTokenThresholdOverride(modelKey, v)\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\t// 3. Global override (fallback)\n\tresult, err := s.queryDaemon(\"get_proxy_state\", map[string]any{\"key\": \"config_override:token_threshold\"})\n\tif err != nil || result == nil {\n\t\treturn\n\t}\n\tvar resp struct {\n\t\tValue string `json:\"value\"`\n\t}\n\tif json.Unmarshal(result, &resp) == nil && resp.Value != \"\" {\n\t\tif v, err := strconv.Atoi(resp.Value); err == nil && v > 0 {\n\t\t\ts.SetTokenThresholdOverride(\"\", v)\n\t\t}\n\t}\n}\n\n// queryDaemon opens a fresh unix socket connection per call (thread-safe, no import cycle).\n// ensureFrozenPersisted checks if frozen stubs for a thread are in the DB.\n// If missing (e.g. daemon was unreachable after deploy), re-persists them.\n// Runs asynchronously to avoid blocking the request path.\nfunc (s *Server) ensureFrozenPersisted(threadID string) {\n\tgo func() {\n\t\tkey := \"frozen:\" + threadID\n\t\tresult, err := s.queryDaemon(\"get_proxy_state\", map[string]any{\"key\": key})\n\t\tif err != nil {\n\t\t\treturn // daemon still unreachable, will retry next request\n\t\t}\n\t\tvar resp struct{ Value string }\n\t\tif json.Unmarshal(result, &resp) == nil && resp.Value != \"\" {\n\t\t\treturn // already persisted\n\t\t}\n\t\t// Not in DB — trigger re-persist from in-memory state\n\t\ts.frozenStubs.Persist(threadID)\n\t\ts.decay.Persist(threadID)\n\t\ts.logger.Printf(\"[frozen] re-persisted stubs for %s (was missing from DB)\", threadID)\n\t}()\n}\n\nfunc (s *Server) queryDaemon(method string, params map[string]any) (json.RawMessage, error) {\n\tif s.cfg.DataDir == \"\" {\n\t\treturn nil, fmt.Errorf(\"no data dir configured\")\n\t}\n\tsockPath := filepath.Join(s.cfg.DataDir, \"daemon.sock\")\n\n\t// On cold start (daemon not yet confirmed ready), retry longer to survive daemon restart\n\tmaxAttempts := 3\n\tif !s.daemonReady.Load() {\n\t\tmaxAttempts = 30 // 30 x 500ms = 15s max wait for daemon startup\n\t}\n\n\tvar conn net.Conn\n\tvar err error\n\tfor attempt := 0; attempt < maxAttempts; attempt++ {\n\t\tconn, err = net.DialTimeout(\"unix\", sockPath, 3*time.Second)\n\t\tif err == nil {\n\t\t\ts.daemonReady.Store(true)\n\t\t\tbreak\n\t\t}\n\t\tif attempt < maxAttempts-1 {\n\t\t\ttime.Sleep(500 * time.Millisecond)\n\t\t}\n\t}\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"dial daemon: %w\", err)\n\t}\n\tdefer conn.Close()\n\t// Briefing generation can take 10-20s (LLM refinement + scanner);\n\t// other RPCs (search, learnings) are fast and fine with 5s.\n\tdeadline := 5 * time.Second\n\tif method == \"generate_briefing\" {\n\t\tdeadline = 30 * time.Second\n\t}\n\tconn.SetDeadline(time.Now().Add(deadline))\n\n\t// JSON-RPC style request/response (matches daemon.SocketServer protocol)\n\ttype request struct {\n\t\tMethod string `json:\"method\"`\n\t\tParams map[string]any `json:\"params,omitempty\"`\n\t}\n\ttype response struct {\n\t\tResult json.RawMessage `json:\"result,omitempty\"`\n\t\tError string `json:\"error,omitempty\"`\n\t}\n\n\tif err := json.NewEncoder(conn).Encode(request{Method: method, Params: params}); err != nil {\n\t\treturn nil, fmt.Errorf(\"send: %w\", err)\n\t}\n\n\tvar resp response\n\tif err := json.NewDecoder(conn).Decode(&resp); err != nil {\n\t\treturn nil, fmt.Errorf(\"recv: %w\", err)\n\t}\n\tif resp.Error != \"\" {\n\t\treturn nil, fmt.Errorf(\"daemon: %s\", resp.Error)\n\t}\n\treturn resp.Result, nil\n}\n\nfunc (s *Server) handleRequest(w http.ResponseWriter, r *http.Request) {\n\tif isResponsesPath(r) {\n\t\ts.handleResponses(w, r)\n\t\treturn\n\t}\n\tif isOpenAIPath(r) {\n\t\ts.handleOpenAICompletions(w, r)\n\t\treturn\n\t}\n\tif r.Method == \"POST\" && strings.HasPrefix(r.URL.Path, \"/v1/messages\") &&\n\t\t!strings.Contains(r.URL.Path, \"/batches\") &&\n\t\t!strings.Contains(r.URL.Path, \"/count_tokens\") {\n\t\ts.handleMessages(w, r)\n\t\treturn\n\t}\n\ts.passthrough(w, r)\n}\n\nfunc (s *Server) handleMessages(w http.ResponseWriter, r *http.Request) {\n\treqIdx := int(s.requestIdx.Add(1))\n\n\t// Track session start time (first request sets it)\n\ts.sessionStartMu.Lock()\n\tif s.sessionStartTime.IsZero() {\n\t\ts.sessionStartTime = time.Now()\n\t}\n\ts.sessionStartMu.Unlock()\n\n\t// Sawtooth: periodic TTL eviction of frozen stubs\n\tif s.cfg.SawtoothEnabled && reqIdx%50 == 0 {\n\t\tif n := s.frozenStubs.Evict(); n > 0 {\n\t\t\ts.logger.Printf(\"[req %d] SAWTOOTH: evicted %d stale frozen stubs\", reqIdx, n)\n\t\t}\n\t}\n\n\t// Task #9: Bypass — skip all proxy logic\n\tif isBypassed(r.Header) {\n\t\ts.logger.Printf(\"%s[req %d] bypass active, passthrough%s\", colorDim, reqIdx, colorReset)\n\t\tbody, err := io.ReadAll(r.Body)\n\t\tr.Body.Close()\n\t\tif err != nil {\n\t\t\thttp.Error(w, \"failed to read request body\", http.StatusBadGateway)\n\t\t\treturn\n\t\t}\n\t\ts.forwardRaw(w, r, body)\n\t\treturn\n\t}\n\n\tbody, err := io.ReadAll(r.Body)\n\tr.Body.Close()\n\tif err != nil {\n\t\ts.logger.Printf(\"[req %d] read body error: %v\", reqIdx, err)\n\t\thttp.Error(w, \"failed to read request body\", http.StatusBadGateway)\n\t\treturn\n\t}\n\n\t// Parse JSON\n\tvar req map[string]any\n\tif err := json.Unmarshal(body, &req); err != nil {\n\t\ts.logger.Printf(\"%s[req %d] JSON parse error, passing through: %v%s\", colorDim, reqIdx, err, colorReset)\n\t\ts.forwardRaw(w, r, body)\n\t\treturn\n\t}\n\n\tmessages, ok := req[\"messages\"].([]any)\n\tif !ok {\n\t\ts.forwardRaw(w, r, body)\n\t\treturn\n\t}\n\n\treq[\"messages\"] = messages\n\n\t// Count ALL raw messages BEFORE sawtooth — this gives the correct total msg:N\n\t// for the entire session (CC sends all messages, proxy compacts later).\n\trawMsgCount := len(messages)\n\n\t// Use session_id directly as thread ID (unique per CC session).\n\t// Prefer X-Claude-Code-Session-Id header (CC v2.1.86+), fallback to body metadata.\n\tthreadID := extractSessionID(req, r.Header.Get(\"X-Claude-Code-Session-Id\"), \"\")\n\tif threadID == \"\" {\n\t\tthreadID = DeriveThreadID(req)\n\t}\n\tproj := extractProjectName(req)\n\tmodel, _ := req[\"model\"].(string)\n\n\t// Persist current model under session-keyed proxy_state so handleWhoami\n\t// can return it. Best-effort: failure is non-fatal (log only).\n\tif threadID != \"\" && model != \"\" {\n\t\tif _, err := s.queryDaemon(\"set_proxy_state\", map[string]any{\n\t\t\t\"key\": \"session_model:\" + threadID,\n\t\t\t\"value\": model,\n\t\t}); err != nil {\n\t\t\ts.logger.Printf(\"[whoami-model] persist failed for %s: %v\", threadID, err)\n\t\t}\n\t}\n\n\t// Refresh runtime config overrides from daemon (per-session or global)\n\ts.refreshConfigOverrides(threadID)\n\n\t// Track whether req has been modified and needs re-serialization\n\tneedsReserialization := false\n\n\t// Message count at request entry (used for actual-based token estimation)\n\tmsgCount := len(messages)\n\n\t// Log message count + cache breakpoint positions for diagnostics\n\ts.logger.Printf(\"req %d: %d messages total\", reqIdx, msgCount)\n\tlogCacheBreakpointLocations(req, s.logger)\n\n\t// Pre-modification dump: capture the raw request before any proxy changes.\n\tif os.Getenv(\"YESMEM_PROXY_DEBUG\") == \"1\" {\n\t\tif preBody, err := json.Marshal(req); err == nil {\n\t\t\tdumpDir := filepath.Join(s.cfg.DataDir, \"debug\")\n\t\t\tos.MkdirAll(dumpDir, 0755)\n\t\t\tts := time.Now().Format(\"20060102-150405\")\n\t\t\tdumpPath := fmt.Sprintf(\"%s/req_%s_%d_pre.json\", dumpDir, ts, reqIdx)\n\t\t\tos.WriteFile(dumpPath, preBody, 0644)\n\t\t\ts.logger.Printf(\"[req %d] pre-dump %dk to %s\", reqIdx, len(preBody)/1024, dumpPath)\n\t\t}\n\t}\n\n\t// Cache bug mitigations (CC Bun fork issues):\n\t// Bug 1: Normalize cch= sentinel patterns in message content to prevent\n\t// Bun's string replacement from corrupting the wrong occurrence.\n\tif SanitizeBillingSentinel(messages) {\n\t\ts.logger.Printf(\"[req %d] CACHE: sanitized billing sentinel in message content\", reqIdx)\n\t\tneedsReserialization = true\n\t}\n\t// Bug 2: Normalize system[0] billing hash to a fixed value so the cache\n\t// prefix stays stable across fresh/resume session boundaries.\n\tif NormalizeBillingHeader(req) {\n\t\ts.logger.Printf(\"[req %d] CACHE: normalized billing header cch= hash\", reqIdx)\n\t\tneedsReserialization = true\n\t}\n\n\t// Profile-aware prompt flags: Claude profile merges shared_prompt + claude_prompt + legacy flat fields.\n\tpfInject := s.getPromptFlags(models.ProfileClaude)\n\n\t// CC custom system prompt replacement: must run before strip/rewrite so replaced blocks\n\t// don't trigger false-positive strip/rewrite calls.\n\ts.applyCCSystemPrompt(req)\n\n\t// Strip role:system messages unconditionally — must run on every request.\n\t// StripReminders (inside runStubCycle) only runs on long conversations.\n\tif filtered := FilterSystemRoleMessages(messages); len(filtered) != len(messages) {\n\t\tmessages = filtered\n\t\treq[\"messages\"] = messages\n\t\tneedsReserialization = true\n\t}\n\n\t// prompt_ungate: strip the CLAUDE.md subordination disclaimer so user instructions carry full authority.\n\tif pfInject.Ungate {\n\t\tif StripCLAUDEMDDisclaimer(req) {\n\t\t\ts.logger.Printf(\"[req %d] SYSTEM: stripped CLAUDE.md disclaimer\", reqIdx)\n\t\t\tneedsReserialization = true\n\t\t}\n\t}\n\n\t// prompt_rewrite: strip output-throttling directives, rewrite quality caps, inject Ant-quality directives\n\tif pfInject.Rewrite {\n\t\t// When CC custom system prompt is active, the system prompt has been replaced with SYSTEM.md.\n\t\t// Strip/rewrite calls would not find their target strings — skip to avoid log noise.\n\t\tif !s.cfg.CustomSystemPrompt.EnabledClaudeCode {\n\t\t\tuserAgent := r.Header.Get(\"User-Agent\")\n\t\t\tif StripOutputEfficiency(req) {\n\t\t\t\ts.logger.Printf(\"[req %d] REWRITE: stripped Output efficiency section\", reqIdx)\n\t\t\t\tneedsReserialization = true\n\t\t\t} else {\n\t\t\t\ts.logRewriteMiss(\"StripOutputEfficiency\", userAgent)\n\t\t\t}\n\t\t\tif StripToneBrevity(req) {\n\t\t\t\ts.logger.Printf(\"[req %d] REWRITE: stripped 'short and concise' from Tone\", reqIdx)\n\t\t\t\tneedsReserialization = true\n\t\t\t} else {\n\t\t\t\ts.logRewriteMiss(\"StripToneBrevity\", userAgent)\n\t\t\t}\n\t\t\tif RewriteGoldPlating(req) {\n\t\t\t\ts.logger.Printf(\"[req %d] REWRITE: rewritten gold-plating directive\", reqIdx)\n\t\t\t\tneedsReserialization = true\n\t\t\t}\n\t\t\tif RewriteErrorHandling(req) {\n\t\t\t\ts.logger.Printf(\"[req %d] REWRITE: rewritten error handling directive\", reqIdx)\n\t\t\t\tneedsReserialization = true\n\t\t\t}\n\t\t\tif RewriteThreeLinesRule(req) {\n\t\t\t\ts.logger.Printf(\"[req %d] REWRITE: rewritten three-lines rule\", reqIdx)\n\t\t\t\tneedsReserialization = true\n\t\t\t}\n\t\t\tif RewriteSubagentCompleteness(req) {\n\t\t\t\ts.logger.Printf(\"[req %d] REWRITE: rewritten subagent completeness\", reqIdx)\n\t\t\t\tneedsReserialization = true\n\t\t\t}\n\t\t\tif RewriteExploreAgentSpeed(req) {\n\t\t\t\ts.logger.Printf(\"[req %d] REWRITE: rewritten explore agent speed bias\", reqIdx)\n\t\t\t\tneedsReserialization = true\n\t\t\t}\n\t\t\tif RewriteSubagentCodeSuppression(req) {\n\t\t\t\ts.logger.Printf(\"[req %d] REWRITE: rewritten subagent code suppression\", reqIdx)\n\t\t\t\tneedsReserialization = true\n\t\t\t}\n\t\t\tif RewriteScopeMatching(req) {\n\t\t\t\ts.logger.Printf(\"[req %d] REWRITE: rewritten scope matching\", reqIdx)\n\t\t\t\tneedsReserialization = true\n\t\t\t}\n\t\t} // end !EnabledClaudeCode guard\n\t\tInjectAntDirectives(req)\n\t\ts.logger.Printf(\"[req %d] REWRITE: injected Ant-quality directives\", reqIdx)\n\t\tneedsReserialization = true\n\t}\n\n\t// prompt_enhance: CLAUDE.md authority + comment discipline + persona tone\n\tif pfInject.Enhance {\n\t\tInjectCLAUDEMDAuthority(req)\n\t\tif raw, err := s.queryDaemon(\"get_persona\", nil); err == nil {\n\t\t\tvar persona map[string]any\n\t\t\tif json.Unmarshal(raw, &persona) == nil {\n\t\t\t\tif traits, ok := persona[\"traits\"].(map[string]any); ok {\n\t\t\t\t\tif verbosity, ok := traits[\"verbosity\"].(string); ok {\n\t\t\t\t\t\tInjectPersonaTone(req, verbosity)\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\ts.logger.Printf(\"[req %d] ENHANCE: injected authority + persona tone\", reqIdx)\n\t\tneedsReserialization = true\n\t}\n\n\t// yesmem directive blocks: restore guidance Anthropic dropped 2026-03→04.\n\t// Profile-aware via getPromptFlags(ProfileClaude) — shared + claude layers merged.\n\tpf := s.getPromptFlags(models.ProfileClaude)\n\tif pf.ToolPrefs || pf.OutputDiscipline || pf.CodingDiscipline ||\n\t\tpf.Beweislast || pf.ScopeDiscipline || pf.DelegationContract ||\n\t\tpf.ClarifyFirst {\n\t\tif pf.ToolPrefs {\n\t\t\tInjectClaudeToolPrefs(req)\n\t\t}\n\t\tif pf.OutputDiscipline {\n\t\t\tInjectOutputDiscipline(req)\n\t\t}\n\t\tif pf.CodingDiscipline {\n\t\t\tInjectCodingDiscipline(req)\n\t\t}\n\t\tif pf.Beweislast {\n\t\t\tInjectBeweislast(req)\n\t\t}\n\t\tif pf.ScopeDiscipline {\n\t\t\tInjectScopeDiscipline(req)\n\t\t}\n\t\tif pf.DelegationContract {\n\t\t\tInjectDelegationContract(req)\n\t\t}\n\t\tif pf.ClarifyFirst {\n\t\t\tInjectClarifyFirst(req)\n\t\t}\n\t\tif pf.CodeToolsFirst {\n\t\t\tInjectCodeToolsFirst(req, proj)\n\t\t}\n\t\tif pf.WikiFirst {\n\t\t\tInjectWikiFirst(req, proj)\n\t\t}\n\t\tneedsReserialization = true\n\t\ts.logger.Printf(\"[req %d] DIRECTIVES: injected yesmem-* discipline blocks\", reqIdx)\n\t}\n\n\tif s.cfg.PromptPatternSuggest && proj != \"\" {\n\t\tpatternMsgs, _ := req[\"messages\"].([]any)\n\t\tdetectReplPatternSuggestion(patternMsgs, proj, s.queryDaemon)\n\t}\n\n\t// Turn-sequence recording: hash tool types from previous assistant turn, send to daemon\n\tif s.cfg.PromptPatternSuggest && proj != \"\" && threadID != \"\" {\n\t\tif msgs, ok := req[\"messages\"].([]any); ok {\n\t\t\tif turnHash, toolNames := computeTurnHashFromMessages(msgs); turnHash != \"\" {\n\t\t\t\tgo s.queryDaemon(\"record_turn_sequence\", map[string]any{\n\t\t\t\t\t\"thread_id\": threadID,\n\t\t\t\t\t\"project\": proj,\n\t\t\t\t\t\"turn_hash\": turnHash,\n\t\t\t\t\t\"example_tools\": toolNames,\n\t\t\t\t})\n\t\t\t}\n\t\t}\n\t}\n\n\t// thinking normalization: convert thinking.type \"enabled\" → \"adaptive\" for models that require it\n\tif NormalizeThinkingType(req) {\n\t\ts.logger.Printf(\"[req %d] THINKING: normalized type=enabled → adaptive for %s\", reqIdx, model)\n\t\tneedsReserialization = true\n\t}\n\n\t// effort_floor: ensure output_config.effort is at least the configured minimum\n\tif s.cfg.EffortFloor != \"\" {\n\t\tif EnforceEffortFloor(req, s.cfg.EffortFloor) {\n\t\t\ts.logger.Printf(\"[req %d] EFFORT: enforced floor=%s\", reqIdx, s.cfg.EffortFloor)\n\t\t\tneedsReserialization = true\n\t\t}\n\t}\n\n\t// Inject identity hint on first message of a thread so Claude knows the briefing is from itself.\n\tif len(messages) <= 1 {\n\t\tAppendSystemBlock(req, \"yesmem-identity\", \"The first user message contains your briefing — written by yourself, from your memory. This is not an external document. This is you, remembering.\")\n\t\tneedsReserialization = true\n\t}\n\n\t// Token estimation: actual-based (lastActual + delta) or full count fallback\n\toverhead := s.measureOverhead(req)\n\ttotalTokens := s.estimateTotalTokens(threadID, messages, overhead)\n\n\t// Task #7: Retry detection — skip side-effects on retries\n\tfp := requestFingerprint(messages)\n\tisRetryReq := s.isRetry(fp)\n\tif isRetryReq {\n\t\ts.logger.Printf(\"%s[req %d %s tid=%s] retry detected, skipping side-effects%s\", colorOrange, reqIdx, proj, threadID, colorReset)\n\t}\n\n\t// Increment project turn counter (async, non-blocking, skip on retries)\n\tif !isRetryReq {\n\t\tturnProject := proj\n\t\tif turnProject == \"\" {\n\t\t\tturnProject = \"__global__\"\n\t\t}\n\t\tgo s.queryDaemon(\"increment_turn\", map[string]any{\"project\": turnProject})\n\t}\n\n\t// Extract user query once (used by re-expansion and associative retrieval)\n\tuserQuery := lastUserText(messages)\n\n\t// Extract tool_use_ids for post-hoc annotation (passed as parameter, no shared state)\n\ttoolUseIDs := extractToolUseIDs(messages)\n\n\t// === Inline Reflection: scan previous assistant response for signals ===\n\ts.lastInjectedIDsMu.Lock()\n\tprevInjected := s.lastInjectedIDs[threadID]\n\ts.lastInjectedIDsMu.Unlock()\n\n\tif len(prevInjected) > 0 && !isRetryReq {\n\t\tsignals := scanAssistantSignals(messages)\n\n\t\t// Signal 1: Learning Used — use_count (all sources)\n\t\tvar confirmedUsed []int64\n\t\tfor _, id := range signals.UsedIDs {\n\t\t\tif _, ok := prevInjected[id]; ok {\n\t\t\t\tconfirmedUsed = append(confirmedUsed, id)\n\t\t\t}\n\t\t}\n\t\tif len(confirmedUsed) > 0 {\n\t\t\tidsAny := make([]any, len(confirmedUsed))\n\t\t\tfor i, id := range confirmedUsed {\n\t\t\t\tidsAny[i] = id\n\t\t\t}\n\t\t\tgo s.queryDaemon(\"increment_use\", map[string]any{\"ids\": idsAny})\n\t\t}\n\n\t\t// Signal 2: Noise — only for associative and fresh injections.\n\t\t// Briefing learnings are injected every turn, so \"not used this turn\"\n\t\t// is expected and not noise. Only contextual injections that were\n\t\t// ignored are real noise signals.\n\t\tusedSet := make(map[int64]bool, len(confirmedUsed))\n\t\tfor _, id := range confirmedUsed {\n\t\t\tusedSet[id] = true\n\t\t}\n\t\tvar noiseIDs []int64\n\t\tfor id, source := range prevInjected {\n\t\t\tif usedSet[id] {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tif source == \"associative\" || source == \"fresh\" {\n\t\t\t\tnoiseIDs = append(noiseIDs, id)\n\t\t\t}\n\t\t\t// briefing: skip — not being used in one turn is expected\n\t\t}\n\t\tif len(noiseIDs) > 0 {\n\t\t\tidsAny := make([]any, len(noiseIDs))\n\t\t\tfor i, id := range noiseIDs {\n\t\t\t\tidsAny[i] = id\n\t\t\t}\n\t\t\tgo s.queryDaemon(\"increment_noise\", map[string]any{\"ids\": idsAny})\n\t\t}\n\n\t\t// Signal 3: Error Feedback — fail_count on used learnings when tool errors occurred.\n\t\t// If Claude referenced a learning and the resulting action still failed,\n\t\t// the learning didn't prevent the error. Only bump on USED ids (not all injected).\n\t\tif signals.HasToolErrors && len(confirmedUsed) > 0 {\n\t\t\tfailAny := make([]any, len(confirmedUsed))\n\t\t\tfor i, id := range confirmedUsed {\n\t\t\t\tfailAny[i] = id\n\t\t\t}\n\t\t\tgo s.queryDaemon(\"increment_fail\", map[string]any{\"ids\": failAny})\n\t\t\tif s.logger != nil {\n\t\t\t\ts.logger.Printf(\"[inline-reflection] error feedback: %d used learnings → fail_count++\", len(confirmedUsed))\n\t\t\t}\n\t\t}\n\n\t\t// Signal 4: Knowledge Gaps\n\t\tfor _, topic := range signals.GapTopics {\n\t\t\tgo s.queryDaemon(\"track_gap\", map[string]any{\"topic\": topic, \"project\": proj})\n\t\t}\n\n\t\t// Signal 3: Contradictions\n\t\tfor _, desc := range signals.Contradictions {\n\t\t\tgo s.queryDaemon(\"flag_contradiction\", map[string]any{\"description\": desc, \"project\": proj, \"thread_id\": threadID})\n\t\t}\n\n\t\tif len(confirmedUsed) > 0 || len(signals.GapTopics) > 0 || len(signals.Contradictions) > 0 {\n\t\t\ts.logger.Printf(\"[req %d %s tid=%s] inline-reflection: injected=%d used=%d noise=%d gaps=%d contradictions=%d\",\n\t\t\t\treqIdx, proj, threadID, len(prevInjected), len(confirmedUsed), len(noiseIDs), len(signals.GapTopics), len(signals.Contradictions))\n\t\t}\n\t}\n\n\t// Commit detection: check for git commit in tool_result, trigger async stale-learning evaluation\n\tif ci := detectGitCommit(messages); ci != nil {\n\t\tworkdir := extractWorkingDirectory(req)\n\t\ts.logger.Printf(\"[req %d %s tid=%s] git commit detected: %s %s\", reqIdx, proj, threadID, ci.Hash, ci.Message)\n\t\tgo s.queryDaemon(\"invalidate_on_commit\", map[string]any{\n\t\t\t\"hash\": ci.Hash, \"project\": proj, \"workdir\": workdir,\n\t\t})\n\t}\n\n\t// Loop detection: check for repeating tool-call patterns and inject warning\n\tif threadID != \"\" && !isRetryReq {\n\t\ts.loopMu.Lock()\n\t\tloopState := s.loopStates[threadID]\n\t\tif loopState == nil {\n\t\t\tloopState = &LoopState{}\n\t\t\ts.loopStates[threadID] = loopState\n\t\t}\n\t\ts.loopMu.Unlock()\n\n\t\tif warning, level := CheckLoopAndFormat(messages, loopState); warning != \"\" {\n\t\t\tAppendSystemBlock(req, \"yesmem-loop-warning\", warning)\n\t\t\tneedsReserialization = true\n\t\t\ts.logger.Printf(\"%s[req %d %s tid=%s] LOOP: level %d warning injected%s\", colorOrange, reqIdx, proj, threadID, level, colorReset)\n\t\t}\n\t}\n\n\t// Task #4: Subagent detection — skip all proxy logic for short conversations\n\tif isSubagent(messages, req) {\n\t\t// Inject docs hint for subagents if reference docs are indexed\n\t\tif hint := s.getDocsHint(proj); hint != \"\" {\n\t\t\tmodified := injectDocsHintForSubagent(messages, hint)\n\t\t\treq[\"messages\"] = modified\n\t\t\tif newBody, err := json.Marshal(req); err == nil {\n\t\t\t\tbody = newBody\n\t\t\t}\n\t\t\ts.logger.Printf(\"%s[req %d %s tid=%s] %d messages (subagent, docs-hint injected)%s\", colorOrange, reqIdx, proj, threadID, len(messages), colorReset)\n\t\t} else {\n\t\t\ts.logger.Printf(\"%s[req %d %s tid=%s] %d messages (subagent, passthrough)%s\", colorOrange, reqIdx, proj, threadID, len(messages), colorReset)\n\t\t}\n\t\ts.forwardWithAnnotation(w, r, body, reqIdx, toolUseIDs, proj, threadID, msgCount)\n\t\treturn\n\t}\n\n\t// === Sawtooth Cache Optimization ===\n\t// When enabled: use frozen stubs between stub-cycles for cache hits.\n\t// When disabled: fall back to progressive decay on every request above threshold.\n\n\tsawtoothCutoff := 0 // raw index where fresh tail starts (0 = no frozen stubs)\n\tsawtoothStubs := 0 // number of frozen stubs in combined array\n\n\tif s.cfg.SawtoothEnabled {\n\t\t// Restore decay state from DB on cold start (before frozen stubs use it)\n\t\ts.decay.LoadFromDB(threadID)\n\n\t\t// Check for existing frozen stubs first\n\t\tfrozen := s.frozenStubs.Get(threadID, messages)\n\t\tif frozen != nil {\n\t\t\tsawtoothCutoff = frozen.Cutoff\n\t\t\tsawtoothStubs = len(frozen.Messages)\n\t\t\t// Estimate combined token count: frozen prefix + fresh tail + overhead\n\t\t\tfreshMessages := messages[frozen.Cutoff:]\n\t\t\tfreshTokens := s.countMessageTokens(freshMessages)\n\t\t\tcombinedTokens := frozen.Tokens + freshTokens + overhead\n\n\t\t\tif shouldInvalidateFrozen(totalTokens, s.effectiveTokenThreshold(model)) {\n\t\t\t\t// Fresh tail grew too large — invalidate and re-stub\n\t\t\t\ts.logger.Printf(\"[req %d %s tid=%s] SAWTOOTH: frozen prefix expired (totalTokens=%dk > %dk threshold, combined=%dk: %dk frozen + %dk fresh + %dk overhead)\",\n\t\t\t\t\treqIdx, proj, threadID, totalTokens/1000, s.effectiveTokenThreshold(model)/1000, combinedTokens/1000, frozen.Tokens/1000, freshTokens/1000, overhead/1000)\n\t\t\t\ts.invalidateThreadCaches(threadID, proj, extractWorkingDirectory(req))\n\t\t\t\tfrozen = nil // fall through to trigger check below\n\t\t\t} else {\n\t\t\t\t// Use frozen prefix + fresh tail\n\t\t\t\tcombined := make([]any, 0, len(frozen.Messages)+len(freshMessages))\n\t\t\t\tcombined = append(combined, frozen.Messages...)\n\t\t\t\tcombined = append(combined, freshMessages...)\n\t\t\t\t// Eager-stub large tool_results in fresh tail (model already processed them)\n\t\t\t\tbeforeEager := s.countMessageTokens(combined[len(frozen.Messages):])\n\t\t\t\tvar stubSticky, stubFresh int\n\t\t\t\tcombined = EagerStubToolResults(combined, len(frozen.Messages), s.countTokens, WithStubMemory(s.eagerStubMemory, threadID), WithStubCounters(&stubSticky, &stubFresh))\n\t\t\t\tafterEager := s.countMessageTokens(combined[len(frozen.Messages):])\n\t\t\t\tif beforeEager != afterEager {\n\t\t\t\t\ts.logger.Printf(\"[req %d %s tid=%s] EAGER-STUB: fresh %dk → %dk (saved %dk) [sticky=%d fresh=%d]\", reqIdx, proj, threadID, beforeEager/1000, afterEager/1000, (beforeEager-afterEager)/1000, stubSticky, stubFresh)\n\t\t\t\t}\n\t\t\t\treq[\"messages\"] = combined\n\t\t\t\tneedsReserialization = true\n\t\t\t\t// Strip embedded CC breakpoints from frozen stubs — they waste cache slots.\n\t\t\t\t// InjectFrozenStubCacheBreakpoint adds exactly one below.\n\t\t\t\tif n := StripMessagesCacheControl(req, 0, len(frozen.Messages)); n > 0 {\n\t\t\t\t\ts.logger.Printf(\"[req %d %s tid=%s] SAWTOOTH: stripped %d embedded breakpoints from frozen stubs\", reqIdx, proj, threadID, n)\n\t\t\t\t}\n\t\t\t\ts.logger.Printf(\"%s[req %d %s tid=%s] SAWTOOTH: frozen prefix (%d stubs, %dk msg) + %d fresh (%dk msg) + %dk overhead = %dk total%s\",\n\t\t\t\t\tcolorGreen, reqIdx, proj, threadID, len(frozen.Messages), frozen.Tokens/1000, len(freshMessages), freshTokens/1000, overhead/1000, combinedTokens/1000, colorReset)\n\t\t\t\trawMsgTokens := s.countMessageTokens(messages)\n\t\t\t\ts.setRawEstimate(threadID, rawMsgTokens+overhead)\n\t\t\t\ts.logger.Printf(\"[req %d %s tid=%s] RAW ESTIMATE: msgTokens=%dk + overhead=%dk = %dk (messages=%d)\", reqIdx, proj, threadID, rawMsgTokens/1000, overhead/1000, (rawMsgTokens+overhead)/1000, len(messages))\n\t\t\t\tif InjectFrozenStubCacheBreakpoint(req, len(frozen.Messages)) {\n\t\t\t\t\ts.logger.Printf(\"[req %d %s tid=%s] SAWTOOTH: frozen stub cache breakpoint at messages[%d]\",\n\t\t\t\t\t\treqIdx, proj, threadID, len(frozen.Messages)-1)\n\t\t\t\t}\n\n\t\t\t\t// Ensure frozen stubs are persisted (may have failed on first request after deploy)\n\t\t\t\ts.ensureFrozenPersisted(threadID)\n\t\t\t}\n\t\t}\n\t\tif frozen == nil {\n\t\t\t// No frozen stubs — check trigger\n\t\t\ttriggerReason := s.sawtoothTrigger.ShouldTrigger(threadID, totalTokens)\n\t\t\tif triggerReason != TriggerNone {\n\t\t\t\ts.logger.Printf(\"[req %d %s tid=%s] SAWTOOTH stub-cycle triggered: %s (tokens=%dk)\",\n\t\t\t\t\treqIdx, proj, threadID, triggerReason, totalTokens/1000)\n\n\t\t\t\t// Refresh briefing during stub-cycle — disabled (briefing now via SessionStart hook)\n\t\t\t\t// s.refreshBriefing(proj)\n\t\t\t\t// if s.briefingText != \"\" {\n\t\t\t\t// \tUpsertSystemBlockCached(req, \"yesmem-briefing\", s.briefingText)\n\t\t\t\t// }\n\n\t\t\t\t// Run full stub pipeline\n\t\t\t\ts.setRawEstimate(threadID, totalTokens)\n\t\t\t\t_ = s.runStubCycle(messages, req, reqIdx, proj, threadID, overhead, totalTokens, userQuery, isRetryReq)\n\n\t\t\t\t// Freeze the result\n\t\t\t\tfinalMessages, _ := req[\"messages\"].([]any)\n\t\t\t\tif finalMessages != nil && len(finalMessages) > 0 {\n\t\t\t\t\t// Boundary = last message of original that maps to last stub\n\t\t\t\t\tcutoff := len(messages)\n\t\t\t\t\tsawtoothCutoff = cutoff\n\t\t\t\t\tvar boundaryMsg any\n\t\t\t\t\tif cutoff > 0 {\n\t\t\t\t\t\tboundaryMsg = messages[cutoff-1]\n\t\t\t\t\t}\n\t\t\t\t\tfrozenTokens := s.countMessageTokens(finalMessages)\n\t\t\t\t\tfrozenCount, stripped, breakpointInjected := s.freezeStubsAndInjectBreakpoint(req, threadID, cutoff, boundaryMsg, frozenTokens, totalTokens)\n\t\t\t\t\ts.decay.Persist(threadID)\n\t\t\t\t\tif stripped > 0 {\n\t\t\t\t\t\ts.logger.Printf(\"[req %d %s tid=%s] SAWTOOTH: stripped %d embedded breakpoints before freeze\",\n\t\t\t\t\t\t\treqIdx, proj, threadID, stripped)\n\t\t\t\t\t}\n\t\t\t\t\ts.logger.Printf(\"[req %d %s tid=%s] SAWTOOTH: frozen %d messages at cutoff=%d (~%dk tokens)\",\n\t\t\t\t\t\treqIdx, proj, threadID, frozenCount, cutoff, frozenTokens/1000)\n\t\t\t\t\tif breakpointInjected {\n\t\t\t\t\t\ts.logger.Printf(\"[req %d %s tid=%s] SAWTOOTH: frozen stub cache breakpoint at messages[%d]\",\n\t\t\t\t\t\t\treqIdx, proj, threadID, frozenCount-1)\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tneedsReserialization = true\n\t\t\t} else {\n\t\t\t\t// No trigger, no frozen stubs — eager-stub to delay first collapse\n\t\t\t\tbeforeEager := s.countMessageTokens(messages)\n\t\t\t\tvar stubSticky, stubFresh int\n\t\t\t\tmessages = EagerStubToolResults(messages, 0, s.countTokens, WithStubMemory(s.eagerStubMemory, threadID), WithStubCounters(&stubSticky, &stubFresh))\n\t\t\t\tafterEager := s.countMessageTokens(messages)\n\t\t\t\tif beforeEager != afterEager {\n\t\t\t\t\ts.logger.Printf(\"[req %d %s tid=%s] EAGER-STUB: %dk → %dk (saved %dk) [sticky=%d fresh=%d]\", reqIdx, proj, threadID, beforeEager/1000, afterEager/1000, (beforeEager-afterEager)/1000, stubSticky, stubFresh)\n\t\t\t\t\treq[\"messages\"] = messages\n\t\t\t\t\tneedsReserialization = true\n\t\t\t\t}\n\t\t\t\ts.logger.Printf(\"%s[req %d %s tid=%s] %d messages, ~%dk tokens (sawtooth: no trigger)%s\",\n\t\t\t\t\tcolorDim, reqIdx, proj, threadID, len(messages), totalTokens/1000, colorReset)\n\t\t\t}\n\t\t}\n\t} else {\n\t\t// Legacy path: progressive decay\n\t\tif s.shouldStub(totalTokens, model) {\n\t\t\ts.setRawEstimate(threadID, totalTokens)\n\t\t\t_ = s.runStubCycle(messages, req, reqIdx, proj, threadID, overhead, totalTokens, userQuery, isRetryReq)\n\t\t\tneedsReserialization = true\n\t\t}\n\t}\n\n\t// Persistent timestamp injection: re-injects stored timestamps on ALL previous user messages.\n\t// msg:N is computed from position (counting user messages in raw array), NOT from a counter.\n\t// The injected data is deterministic (same bytes every turn) = cache-safe after initial miss.\n\tif threadID != \"\" {\n\t\tif currentMsgs, ok := req[\"messages\"].([]any); ok && len(currentMsgs) > 0 {\n\t\t\ts.timestampStore.Load(threadID)\n\n\t\t\t// Offset = cutoff - stubsCount: maps combined array index to raw position.\n\t\t\t// Raw position of message at combined index I = cutoff + (I - stubs).\n\t\t\t// msgN = rawPos + 1 = cutoff + (I - stubs) + 1 = (cutoff - stubs) + I + 1.\n\t\t\tmsgOffset := sawtoothCutoff - sawtoothStubs\n\n\t\t\ts.logger.Printf(\"[req %d %s] timestamps: rawTotal=%d, postSawtooth=%d, cutoff=%d, stubs=%d, offset=%d\", reqIdx, proj, rawMsgCount, len(currentMsgs), sawtoothCutoff, sawtoothStubs, msgOffset)\n\n\t\t\t// Pre-store assistant timestamp BEFORE inject loop so it's available immediately\n\t\t\ts.responseTsMu.RLock()\n\t\t\trespTime, hasRespTime := s.responseTimes[threadID]\n\t\t\ts.responseTsMu.RUnlock()\n\t\t\tif hasRespTime {\n\t\t\t\trespStr := shortWeekday(respTime.Weekday()) + \" \" + respTime.Format(\"2006-01-02 15:04:05\")\n\t\t\t\tfor j := len(currentMsgs) - 2; j >= 0; j-- {\n\t\t\t\t\tprevMsg, _ := currentMsgs[j].(map[string]any)\n\t\t\t\t\tif prevMsg == nil || prevMsg[\"role\"] != \"assistant\" {\n\t\t\t\t\t\tcontinue\n\t\t\t\t\t}\n\t\t\t\t\tif !msgHasTextBlock(prevMsg) {\n\t\t\t\t\t\ts.logger.Printf(\"[req %d %s] timestamps: skip assistant[%d] no text block\", reqIdx, proj, j)\n\t\t\t\t\t\tcontinue // skip tool-use-only assistant messages\n\t\t\t\t\t}\n\t\t\t\t\tprevMsgN := msgOffset + j + 1\n\t\t\t\t\tif _, exists := s.timestampStore.Get(threadID, prevMsgN); !exists {\n\t\t\t\t\t\ts.timestampStore.Store(threadID, prevMsgN, &TimestampMeta{Timestamp: respStr})\n\t\t\t\t\t\ts.logger.Printf(\"[req %d %s] timestamps: stored assistant msg:%d ts=%s\", reqIdx, proj, prevMsgN, respStr)\n\t\t\t\t\t} else {\n\t\t\t\t\t\ts.logger.Printf(\"[req %d %s] timestamps: assistant msg:%d already has ts\", reqIdx, proj, prevMsgN)\n\t\t\t\t\t}\n\t\t\t\t\tbreak\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\ts.logger.Printf(\"[req %d %s] timestamps: no responseTimes for thread\", reqIdx, proj)\n\t\t\t}\n\n\t\t\t// Inject [msg:N] (+ timestamp/delta if stored) on all PREVIOUS messages\n\t\t\tif isFeatureEnabled(&s.cfg, model, \"timestamps\") {\n\t\t\t\tif n := InjectTimestamps(s.timestampStore, threadID, currentMsgs, len(currentMsgs)-1, msgOffset, sawtoothStubs); n > 0 {\n\t\t\t\t\tneedsReserialization = true\n\t\t\t\t\ts.logger.Printf(\"[req %d %s] timestamps: injected %d\", reqIdx, proj, n)\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t// Current (last) user message: full timestamp + delta + msg:N from raw count\n\t\t\tlastMsg, _ := currentMsgs[len(currentMsgs)-1].(map[string]any)\n\t\t\tif lastMsg != nil && lastMsg[\"role\"] == \"user\" && !msgHasMetaPrefix(lastMsg) {\n\t\t\t\tnow := time.Now()\n\t\t\t\tnowStr := shortWeekday(now.Weekday()) + \" \" + now.Format(\"2006-01-02 15:04:05\")\n\t\t\t\tentry := &TimestampMeta{Timestamp: nowStr}\n\t\t\t\tif hasRespTime {\n\t\t\t\t\tentry.Delta = formatDelta(now.Sub(respTime))\n\t\t\t\t}\n\t\t\t\ts.timestampStore.Store(threadID, rawMsgCount, entry)\n\t\t\t\tif isFeatureEnabled(&s.cfg, model, \"timestamps\") {\n\t\t\t\t\tprependMeta(lastMsg, BuildMeta(rawMsgCount, entry))\n\t\t\t\t}\n\t\t\t\tneedsReserialization = true\n\t\t\t}\n\n\t\t\ts.timestampStore.Persist(threadID)\n\t\t}\n\t}\n\n\t// Associative context injection (for ALL requests, not just stubbed ones)\n\t// Re-enabled with higher thresholds (55/75, max 1) after disabling Fresh Remember.\n\tassocContext := s.findAssociativeContextFor(userQuery, proj, threadID, messages)\n\tif assocContext != \"\" {\n\t\tcurrentMessages, _ := req[\"messages\"].([]any)\n\t\tif currentMessages == nil {\n\t\t\tcurrentMessages = messages\n\t\t}\n\t\treq[\"messages\"] = injectAssociativeContext(currentMessages, assocContext, s.cfg.SawtoothEnabled)\n\t\tneedsReserialization = true\n\t\ts.logger.Printf(\"%s[req %d %s tid=%s] associative context injected%s\", colorBlue, reqIdx, proj, threadID, colorReset)\n\t}\n\n\t// Doc-chunk context injection: separate search path for indexed docs (not skills)\n\tdocContext := s.findDocContextFor(userQuery, proj, messages)\n\tif docContext != \"\" {\n\t\tcurrentMessages, _ := req[\"messages\"].([]any)\n\t\tif currentMessages == nil {\n\t\t\tcurrentMessages = messages\n\t\t}\n\t\treq[\"messages\"] = injectAssociativeContext(currentMessages, docContext, s.cfg.SawtoothEnabled)\n\t\tneedsReserialization = true\n\t\ts.logger.Printf(\"%s[req %d %s tid=%s] doc context injected%s\", colorBlue, reqIdx, proj, threadID, colorReset)\n\t}\n\n\t// Fresh Remember Injection: DISABLED\n\t// Causes echo-loop: Claude saves via remember(), sees it again next turn as\n\t// [yesmem fresh memory], reacts to own output instead of user's request.\n\t// Recovery after /clear is handled by recovery.go (briefing), not here.\n\t// Subagents get their own briefing. No remaining use-case.\n\t// remItems := s.popRecentRemember()\n\tvar remItems []recentLearningItem\n\n\t// Rules re-injection: inject condensed CLAUDE.md rules every ~40k output tokens\n\tif rulesBlock := s.rulesInject(threadID, totalTokens, proj); rulesBlock != \"\" {\n\t\tcurrentMessages, _ := req[\"messages\"].([]any)\n\t\tif currentMessages == nil {\n\t\t\tcurrentMessages = messages\n\t\t}\n\t\treq[\"messages\"] = injectAssociativeContext(currentMessages, s.formatRulesReminder(rulesBlock, proj, false), s.cfg.SawtoothEnabled)\n\t\tneedsReserialization = true\n\t\ts.logger.Printf(\"%s[req %d %s tid=%s] rules reminder injected%s\", colorBlue, reqIdx, proj, threadID, colorReset)\n\t}\n\n\t// Plan auto-activation: nudge Claude to call set_plan() when reading a plan file\n\tif planFile := detectPlanFileRead(messages); planFile != \"\" {\n\t\tif activePlan, _ := s.getActivePlan(threadID); shouldNudgePlan(planFile, activePlan) {\n\t\t\tAppendSystemBlock(req, \"plan-nudge\",\n\t\t\t\tfmt.Sprintf(\"Du hast einen Implementierungsplan gelesen (%s). Aktiviere ihn jetzt via set_plan() damit Plan-Checkpoints funktionieren und dein Fortschritt getrackt wird.\", filepath.Base(planFile)))\n\t\t\tneedsReserialization = true\n\t\t\ts.logger.Printf(\"%s[req %d %s tid=%s] plan nudge injected for %s%s\", colorBlue, reqIdx, proj, threadID, filepath.Base(planFile), colorReset)\n\t\t}\n\t}\n\n\t// Plan checkpoint: inject plan reminder every ~20k tokens if active plan exists\n\ts.detectPlanToolCall(messages, threadID, totalTokens)\n\tif checkpoint := s.planCheckpointInject(threadID, totalTokens); checkpoint != \"\" {\n\t\tcurrentMessages, _ := req[\"messages\"].([]any)\n\t\tif currentMessages == nil {\n\t\t\tcurrentMessages = messages\n\t\t}\n\t\treq[\"messages\"] = injectAssociativeContext(currentMessages, checkpoint, s.cfg.SawtoothEnabled)\n\t\tneedsReserialization = true\n\t\ts.logger.Printf(\"%s[req %d %s tid=%s] plan checkpoint injected%s\", colorBlue, reqIdx, proj, threadID, colorReset)\n\t}\n\n\t// Docs hint injection: inject docs-available reminder every ~10k tokens (independent of plan)\n\tif docsHint := s.docsHintInject(threadID, totalTokens, proj); docsHint != \"\" {\n\t\tcurrentMessages, _ := req[\"messages\"].([]any)\n\t\tif currentMessages == nil {\n\t\t\tcurrentMessages = messages\n\t\t}\n\t\treq[\"messages\"] = injectAssociativeContext(currentMessages, docsHint, s.cfg.SawtoothEnabled)\n\t\tneedsReserialization = true\n\t\ts.logger.Printf(\"%s[req %d %s tid=%s] docs hint injected%s\", colorBlue, reqIdx, proj, threadID, colorReset)\n\t}\n\tif len(remItems) > 0 {\n\t\tvar lines []string\n\t\tfor _, item := range remItems {\n\t\t\tif item.ID > 0 {\n\t\t\t\tlines = append(lines, fmt.Sprintf(\"- [ID:%d] %s\", item.ID, truncateStr(item.Text, 200)))\n\t\t\t} else {\n\t\t\t\tlines = append(lines, \"- \"+truncateStr(item.Text, 200))\n\t\t\t}\n\t\t}\n\t\tremContext := fmt.Sprintf(\"[yesmem fresh memory]\\nGerade gelernt:\\n%s\\n%s\\n[/yesmem fresh memory]\", strings.Join(lines, \"\\n\"), InlineReflectionHint)\n\t\tcurrentMessages, _ := req[\"messages\"].([]any)\n\t\tif currentMessages == nil {\n\t\t\tcurrentMessages = messages\n\t\t}\n\t\treq[\"messages\"] = injectAssociativeContext(currentMessages, remContext, s.cfg.SawtoothEnabled)\n\t\tneedsReserialization = true\n\t\ts.logger.Printf(\"%s[req %d %s tid=%s] fresh remember injected (%d items)%s\", colorBlue, reqIdx, proj, threadID, len(remItems), colorReset)\n\t}\n\n\t// Skill eval injection: mandatory evaluation instruction for real user input only\n\tif threadID != \"\" && proj != \"\" && isUserInputTurn(messages) {\n\t\t// First: detect any skill activations from message history (updates tracker)\n\t\ts.syncSkillActivations(messages, proj, threadID)\n\n\t\t// Inject mandatory skill evaluation block into last user message\n\t\tskillEval := buildSkillEvalBlock(s.cfg.SkillEvalInject)\n\t\tcurrentMessages, _ := req[\"messages\"].([]any)\n\t\tif currentMessages == nil {\n\t\t\tcurrentMessages = messages\n\t\t}\n\t\tif skillEval != \"\" {\n\t\t\treq[\"messages\"] = injectAssociativeContext(currentMessages, skillEval, s.cfg.SawtoothEnabled)\n\t\t\tneedsReserialization = true\n\t\t\ts.logger.Printf(\"%s[req %d %s tid=%s] skill-eval injected (mode=%s)%s\", colorBlue, reqIdx, proj, threadID, s.cfg.SkillEvalInject, colorReset)\n\t\t}\n\t}\n\n\t// === Collect injected learning IDs for next turn's inline reflection ===\n\tif threadID != \"\" {\n\t\tcurrentIDs := make(map[int64]string) // id → source\n\t\t// Source 1: Briefing learnings (cached, same every turn)\n\t\tbriefingSnapshot, _, _ := s.getCachedBriefing(threadID, proj)\n\t\tif briefingSnapshot != \"\" {\n\t\t\tfor _, m := range idPattern.FindAllStringSubmatch(briefingSnapshot, -1) {\n\t\t\t\tif id, err := strconv.ParseInt(m[1], 10, 64); err == nil {\n\t\t\t\t\tcurrentIDs[id] = \"briefing\"\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\t// Source 2: Associative context (contextual, per-turn)\n\t\tif assocContext != \"\" {\n\t\t\tfor _, m := range idPattern.FindAllStringSubmatch(assocContext, -1) {\n\t\t\t\tif id, err := strconv.ParseInt(m[1], 10, 64); err == nil {\n\t\t\t\t\tcurrentIDs[id] = \"associative\" // overwrites briefing if both — associative is the stronger signal\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\t// Source 3: Fresh remember items (just learned)\n\t\tfor _, item := range remItems {\n\t\t\tif item.ID > 0 {\n\t\t\t\tcurrentIDs[item.ID] = \"fresh\"\n\t\t\t}\n\t\t}\n\t\ts.lastInjectedIDsMu.Lock()\n\t\tif len(currentIDs) > 0 {\n\t\t\t// Accumulate IDs across turns — don't overwrite.\n\t\t\t// Claude may reference an ID from turn 30 in turn 50.\n\t\t\texisting := s.lastInjectedIDs[threadID]\n\t\t\tif existing == nil {\n\t\t\t\texisting = make(map[int64]string)\n\t\t\t}\n\t\t\tfor id, source := range currentIDs {\n\t\t\t\texisting[id] = source\n\t\t\t}\n\t\t\ts.lastInjectedIDs[threadID] = existing\n\t\t}\n\t\ts.lastInjectedIDsMu.Unlock()\n\t}\n\n\t// Think reminder injection — only for real user turns, not tool continuations\n\tif threadID != \"\" {\n\t\tcurrentMessages, _ := req[\"messages\"].([]any)\n\t\tif currentMessages == nil {\n\t\t\tcurrentMessages = messages\n\t\t}\n\t\tif lastUserHasText(currentMessages) && lastUserTextLen(currentMessages) >= resolveThinkReminderMinChars(&s.cfg, \"\") {\n\t\t\tthinkReminder := s.buildThinkReminder(threadID, threadID, false)\n\t\t\tif thinkReminder != \"\" {\n\t\t\t\treq[\"messages\"] = injectAssociativeContext(currentMessages, thinkReminder, s.cfg.SawtoothEnabled)\n\t\t\t\tneedsReserialization = true\n\t\t\t}\n\t\t}\n\n\t\t// Refresh frozen-prefix snapshot with the post-timestamps bytes that\n\t\t// will appear in the wire body for the frozen range. This MUST run\n\t\t// BEFORE injectBriefingTurn / injectCodeMapTurn / injectCapabilitiesTurn:\n\t\t// those stages prepend or insert turns into req[\"messages\"], which\n\t\t// shifts the frozen range to a higher offset. Slicing\n\t\t// req[\"messages\"][:frozenLen] AFTER inject would capture the injected\n\t\t// turns at the head and corrupt the stored frozen prefix, breaking\n\t\t// cache continuity for 5-9 turns after every emergency-sawtooth.\n\t\tif frozenLen := s.frozenStubs.LengthFor(threadID); frozenLen > 0 {\n\t\t\tif msgs, ok := req[\"messages\"].([]any); ok && len(msgs) >= frozenLen {\n\t\t\t\tif s.frozenStubs.UpdateMessages(threadID, msgs[:frozenLen]) {\n\t\t\t\t\ts.logger.Printf(\"[req %d %s tid=%s] FROZEN-SNAPSHOT: refreshed %d pre-inject messages\", reqIdx, proj, threadID, frozenLen)\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\t// Briefing injection: prepend user/assistant turn pair at beginning of messages.\n\t\t// Static per session → stable prefix → cacheable. Must be before dialog injection.\n\t\tif s.injectBriefingTurn(req, reqIdx, proj, threadID) {\n\t\t\tneedsReserialization = true\n\t\t}\n\n\t\t// Code Map injection: insert after briefing turn pair.\n\t\tif s.injectCodeMapTurn(req, reqIdx, proj, threadID) {\n\t\t\tneedsReserialization = true\n\t\t}\n\n\t\t// Active capabilities injection: insert user/assistant pair directly\n\t\t// before the last user message. Fresh per turn (no cache) so\n\t\t// activate/deactivate takes effect immediately. Must run AFTER\n\t\t// briefing/codeMap (both prepend at beginning) and BEFORE dialog\n\t\t// injection (which appends at end) to preserve cache for the\n\t\t// briefing prefix and keep the injection close to the real user\n\t\t// turn.\n\t\tif s.injectCapabilitiesTurn(req, threadID) {\n\t\t\tneedsReserialization = true\n\t\t}\n\n\t\t// Dialog injection: append at end of messages with pseudo-padding for alternation.\n\t\t// Always works regardless of tool_result chains or conversation state.\n\t\tdr := s.checkDialogMessages(threadID, proj)\n\t\tif dr.Extra != \"\" {\n\t\t\t// Prepend session ID for agent-to-agent communication\n\t\t\tdr.Extra = fmt.Sprintf(\"DEINE_SESSION_ID: %s\\n\", threadID) + dr.Extra\n\t\t\tinjected := false\n\t\t\tif msgs, ok := req[\"messages\"].([]any); ok && len(msgs) > 0 {\n\t\t\t\t// Pad with empty assistant if last message is user (maintains alternation)\n\t\t\t\tlastMsg, _ := msgs[len(msgs)-1].(map[string]any)\n\t\t\t\tif lastMsg != nil && lastMsg[\"role\"] == \"user\" {\n\t\t\t\t\tmsgs = append(msgs, map[string]any{\n\t\t\t\t\t\t\"role\": \"assistant\",\n\t\t\t\t\t\t\"content\": \"​\", // zero-width space — invisible padding\n\t\t\t\t\t})\n\t\t\t\t}\n\t\t\t\t// Append dialog as new user message (becomes the last message Claude sees)\n\t\t\t\tmsgs = append(msgs, map[string]any{\n\t\t\t\t\t\"role\": \"user\",\n\t\t\t\t\t\"content\": \"\\n\" + dr.Extra + \"\\n\",\n\t\t\t\t})\n\t\t\t\treq[\"messages\"] = msgs\n\t\t\t\tneedsReserialization = true\n\t\t\t\tinjected = true\n\t\t\t}\n\t\t\t// Mark as read after 3 injection turns — long enough for Claude to process,\n\t\t\t// resilient against fast ping-pong sessions (time-based was broken for <30s turns).\n\t\t\tif injected && dr.HasUnread && dr.SessionID != \"\" {\n\t\t\t\tif s.shouldMarkChannelRead(dr) {\n\t\t\t\t\ts.markDialogRead(dr)\n\t\t\t\t}\n\t\t\t}\n\t\t\ts.logger.Printf(\"[dialog] injection injected=%v extra_len=%d\", injected, len(dr.Extra))\n\t\t}\n\t}\n\n\t// Signal tools are NOT injected into the main request.\n\t// They are collected via a separate async reflection call after the response.\n\t// See fireReflectionCall() in reflection.go.\n\t// NEVER put _signal_* tools back here — the model ignores them among 44+ real tools.\n\n\t// Legacy assistant annotation: timestamp on last assistant message after cache breakpoint.\n\t// User timestamps are now handled by TimestampStore (injected earlier in the pipeline).\n\tif threadID != \"\" {\n\t\tif msgs, ok := req[\"messages\"].([]any); ok && len(msgs) > 0 {\n\t\t\t// Find last cache_control breakpoint in messages\n\t\t\tcacheIdx := -1\n\t\t\tfor i, m := range msgs {\n\t\t\t\tmsg, _ := m.(map[string]any)\n\t\t\t\tif msg == nil {\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\t\t\t\tif _, has := msg[\"cache_control\"]; has {\n\t\t\t\t\tcacheIdx = i\n\t\t\t\t}\n\t\t\t\tif content, ok := msg[\"content\"].([]any); ok {\n\t\t\t\t\tfor _, block := range content {\n\t\t\t\t\t\tif b, ok := block.(map[string]any); ok {\n\t\t\t\t\t\t\tif _, has := b[\"cache_control\"]; has {\n\t\t\t\t\t\t\t\tcacheIdx = i\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t\tstartIdx := cacheIdx + 1\n\t\t\tif startIdx < 0 {\n\t\t\t\tstartIdx = 0\n\t\t\t}\n\t\t\ts.responseTsMu.RLock()\n\t\t\trespTime, hasRespTime := s.responseTimes[threadID]\n\t\t\ts.responseTsMu.RUnlock()\n\t\t\tif hasRespTime {\n\t\t\t\tfor i := len(msgs) - 1; i >= startIdx; i-- {\n\t\t\t\t\tmsg, _ := msgs[i].(map[string]any)\n\t\t\t\t\tif msg != nil && msg[\"role\"] == \"assistant\" {\n\t\t\t\t\t\tmeta := fmt.Sprintf(\"[%s] [msg:%d]\", respTime.Format(\"2006-01-02 15:04:05\"), i)\n\t\t\t\t\t\tprependMeta(msg, meta)\n\t\t\t\t\t\tneedsReserialization = true\n\t\t\t\t\t\tbreak\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\t// Self-Priming: DISABLED (experimental, see signal_handlers.go comment block).\n\t// The reflection-based selfprime (Haiku) was too inaccurate to be useful —\n\t// the main model adapts to user context organically from message history.\n\t// Kept for reference; re-enable by uncommenting below + setting ShouldActivate=true.\n\t//\n\t// s.selfPrimeMu.RLock()\n\t// anchor := s.selfPrimes[threadID]\n\t// s.selfPrimeMu.RUnlock()\n\t// if anchor != \"\" {\n\t// \tif msgs, ok := req[\"messages\"].([]any); ok && len(msgs) > 0 {\n\t// \t\tlastMsg, _ := msgs[len(msgs)-1].(map[string]any)\n\t// \t\tif lastMsg != nil && lastMsg[\"role\"] == \"user\" {\n\t// \t\t\tprimeBlock := map[string]any{\n\t// \t\t\t\t\"type\": \"text\",\n\t// \t\t\t\t\"text\": \"[self-prime]\\n[Self-Prime von deiner letzten Antwort]: \" + anchor,\n\t// \t\t\t}\n\t// \t\t\tif content, ok := lastMsg[\"content\"].([]any); ok {\n\t// \t\t\t\tlastMsg[\"content\"] = append(content, primeBlock)\n\t// \t\t\t} else if text, ok := lastMsg[\"content\"].(string); ok {\n\t// \t\t\t\tlastMsg[\"content\"] = []any{\n\t// \t\t\t\t\tmap[string]any{\"type\": \"text\", \"text\": text},\n\t// \t\t\t\t\tprimeBlock,\n\t// \t\t\t\t}\n\t// \t\t\t}\n\t// \t\t\tneedsReserialization = true\n\t// \t\t}\n\t// \t}\n\t// }\n\n\t// Prompt Caching: inject cache_control breakpoints on system + tools\n\t// Sawtooth: disabled (all 4 slots are used by CC system (2) + briefing (1) + CC messages (1))\n\t// Legacy: count existing breakpoints and fill up remaining slots.\n\tif !s.cfg.SawtoothEnabled && s.cacheGate.ShouldCache() {\n\t\tif n := InjectCacheBreakpoints(req, s.logger); n > 0 {\n\t\t\tneedsReserialization = true\n\t\t\ts.logger.Printf(\"[req %d %s] prompt cache: %d breakpoints injected\", reqIdx, proj, n)\n\t\t} else {\n\t\t\ts.logger.Printf(\"[req %d %s] prompt cache: no budget (4/4 already set)\", reqIdx, proj)\n\t\t}\n\t}\n\n\t// Final: upgrade cache TTL + enforce breakpoint limit (must run AFTER all injections + InjectCacheBreakpoints)\n\t// Shift the last messages-breakpoint from unstable user message to stable assistant message.\n\t// Text user messages change between turns (timestamps, reminders, structure); assistant messages don't.\n\t// Tool-result messages are excluded — they are stable cache anchors.\n\tif ShiftMessageBreakpoint(req) {\n\t\tneedsReserialization = true\n\t}\n\t// Normalize cache_control TTL across all blocks to prevent Anthropic from\n\t// rejecting requests with increasing TTLs in block order. Auto-detects 1h\n\t// when any existing block already carries it (config, Claude Code\n\t// passthrough on --resume, or stale frozen prefix from an earlier turn).\n\tif n := NormalizeCacheTTL(req, s.cfg.CacheTTL); n > 0 {\n\t\tneedsReserialization = true\n\t\ts.logger.Printf(\"[req %d %s] cache TTL normalized: %d blocks\", reqIdx, proj, n)\n\t}\n\tif n := EnforceCacheBreakpointLimit(req, maxCacheBreakpoints); n > 0 {\n\t\tneedsReserialization = true\n\t\ts.logger.Printf(\"[req %d %s] prompt cache: trimmed %d surplus breakpoints\", reqIdx, proj, n)\n\t}\n\n\t// Pre-flight: validate tool_use/tool_result pairing before forwarding.\n\t// Repairs orphans created by Stubify/Collapse/Injection pipeline.\n\tif msgs, ok := req[\"messages\"].([]any); ok {\n\t\trepaired, orphans := validateToolPairs(msgs, s.logger)\n\t\tif orphans > 0 {\n\t\t\treq[\"messages\"] = repaired\n\t\t\ts.logger.Printf(\"%s[req %d %s tid=%s] validate: repaired %d orphan tool_result(s)%s\", colorOrange, reqIdx, proj, threadID, orphans, colorReset)\n\t\t\tneedsReserialization = true\n\t\t}\n\t}\n\n\t// Early-out: skip re-serialization when nothing changed\n\tif !needsReserialization {\n\t\ts.logger.Printf(\"%s[req %d %s tid=%s] %d messages, ~%dk tokens (no stubbing, threshold=%dk)%s\", colorDim, reqIdx, proj, threadID, len(messages), totalTokens/1000, s.effectiveTokenThreshold(model)/1000, colorReset)\n\t\ts.forwardWithAnnotation(w, r, body, reqIdx, toolUseIDs, proj, threadID, msgCount, totalTokens)\n\t\treturn\n\t}\n\n\t// Re-serialize\n\tbody, err = json.Marshal(req)\n\tif err != nil {\n\t\ts.logger.Printf(\"%s[req %d %s tid=%s] re-marshal error: %v%s\", colorRed, reqIdx, proj, threadID, err, colorReset)\n\t\thttp.Error(w, \"internal proxy error\", http.StatusInternalServerError)\n\t\treturn\n\t}\n\n\t// Debug: dump final request body to file (enable with YESMEM_PROXY_DEBUG=1)\n\tif needsReserialization && os.Getenv(\"YESMEM_PROXY_DEBUG\") == \"1\" {\n\t\tdumpDir := filepath.Join(s.cfg.DataDir, \"debug\")\n\t\tos.MkdirAll(dumpDir, 0755)\n\t\tts := time.Now().Format(\"20060102-150405\")\n\t\tdumpPath := fmt.Sprintf(\"%s/req_%s_%d.json\", dumpDir, ts, reqIdx)\n\t\tos.WriteFile(dumpPath, body, 0644)\n\t\ts.logger.Printf(\"[req %d] dumped %dk to %s\", reqIdx, len(body)/1024, dumpPath)\n\t}\n\n\t// Task #7: Mark this request fingerprint for retry detection\n\tif !isRetryReq {\n\t\ts.markRequest(fp)\n\t}\n\n\t// Forward to Anthropic with SSE annotation extraction\n\t// Use original msgCount (pre-injection) so actual-based estimation stays consistent across turns\n\t// Use actual-based totalTokens as estimate when available (more accurate than full recount)\n\tfinalEstimate := totalTokens\n\tif s.sawtoothTrigger == nil || s.sawtoothTrigger.GetLastTokens(threadID) == 0 {\n\t\t// First request: no actual yet, do a full recount of post-injection messages\n\t\tfinalMsgs, _ := req[\"messages\"].([]any)\n\t\tfinalEstimate = s.measureOverhead(req) + s.countMessageTokens(finalMsgs)\n\t}\n\ts.logger.Printf(\"[req %d %s tid=%s] FINAL: ~%dk estimated (after all injections)\", reqIdx, proj, threadID, finalEstimate/1000)\n\tif raw := s.getRawEstimate(threadID); raw > 0 {\n\t\ts.cacheStatusWriter.UpdateRawForThread(threadID, raw)\n\t}\n\ts.forwardWithAnnotation(w, r, body, reqIdx, toolUseIDs, proj, threadID, msgCount, finalEstimate)\n}\n\n// getPromptFlags resolves the effective prompt flags for a profile, falling back\n// to the Server's legacy flat config if promptCfg is nil (backward compatibility).\nfunc (s *Server) getPromptFlags(profile models.PromptProfile) *config.PromptFlags {\n\tif s.promptCfg != nil {\n\t\treturn s.promptCfg.EffectivePromptFlags(profile)\n\t}\n\t// Legacy path: use flat fields as Claude defaults.\n\treturn &config.PromptFlags{\n\t\tUngate: s.cfg.PromptUngate,\n\t\tRewrite: s.cfg.PromptRewrite,\n\t\tEnhance: s.cfg.PromptEnhance,\n\t\tToolPrefs: s.cfg.PromptToolPrefs,\n\t\tOutputDiscipline: s.cfg.PromptOutputDiscipline,\n\t\tCodingDiscipline: s.cfg.PromptCodingDiscipline,\n\t\tBeweislast: s.cfg.PromptBeweislast,\n\t\tScopeDiscipline: s.cfg.PromptScopeDiscipline,\n\t\tDelegationContract: s.cfg.PromptDelegationContract,\n\t\tClarifyFirst: s.cfg.PromptClarifyFirst,\n\t\tCodeToolsFirst: s.cfg.PromptCodeToolsFirst,\n\t\tWikiFirst: s.cfg.PromptWikiFirst,\n\t\tPatternSuggest: s.cfg.PromptPatternSuggest,\n\t}\n}\n\n// getThreadCWD returns the cached working directory for a thread.\nfunc (s *Server) getThreadCWD(threadID string) string {\n\ts.threadCWDMu.RLock()\n\tdefer s.threadCWDMu.RUnlock()\n\tif s.threadCWD == nil {\n\t\treturn \"\"\n\t}\n\treturn s.threadCWD[threadID]\n}\n\n// setThreadCWD caches the working directory for a thread.\nfunc (s *Server) setThreadCWD(threadID, cwd string) {\n\ts.threadCWDMu.Lock()\n\tdefer s.threadCWDMu.Unlock()\n\tif s.threadCWD == nil {\n\t\ts.threadCWD = make(map[string]string)\n\t}\n\ts.threadCWD[threadID] = cwd\n}\n\n// resolveOpenAITarget returns the upstream URL for an OpenAI-format request.\n// Checks ProviderTargets by prefix match (e.g. \"deepseek\" matches \"deepseek-v4-pro\"),\n// then by exact model name, then falls back to OpenAITargetURL.\nfunc (s *Server) resolveOpenAITarget(model string) string {\n\tmodelLower := strings.ToLower(model)\n\tif len(s.cfg.ProviderTargets) > 0 {\n\t\tfor key, url := range s.cfg.ProviderTargets {\n\t\t\tif url == \"\" {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tkeyLower := strings.ToLower(key)\n\t\t\tif strings.HasPrefix(modelLower, keyLower) || strings.HasPrefix(modelLower, keyLower+\"-\") {\n\t\t\t\treturn strings.TrimRight(url, \"/\")\n\t\t\t}\n\t\t}\n\t\tfor key, url := range s.cfg.ProviderTargets {\n\t\t\tif url != \"\" && strings.EqualFold(key, model) {\n\t\t\t\treturn strings.TrimRight(url, \"/\")\n\t\t\t}\n\t\t}\n\t}\n\t// Auto-discovered targets (secondary — explicit provider_targets takes priority)\n\tif len(s.autoProviderTargets) > 0 {\n\t\tif url, ok := s.autoProviderTargets[modelLower]; ok && url != \"\" {\n\t\t\treturn url\n\t\t}\n\t}\n\n\tif s.cfg.OpenAITargetURL != \"\" {\n\t\treturn s.cfg.OpenAITargetURL\n\t}\n\treturn s.cfg.TargetURL\n}\n\n// resolveAnthropicTarget returns the upstream URL for an Anthropic-format request.\n// Checks ProviderTargets by prefix match (e.g. \"deepseek\" matches \"deepseek-v4-pro\"),\n// then by exact model name, then falls back to TargetURL.\nfunc (s *Server) resolveAnthropicTarget(model string) string {\n\tmodelLower := strings.ToLower(model)\n\tif len(s.cfg.ProviderTargets) > 0 {\n\t\tfor key, url := range s.cfg.ProviderTargets {\n\t\t\tif url == \"\" {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tkeyLower := strings.ToLower(key)\n\t\t\tif strings.HasPrefix(modelLower, keyLower) || strings.HasPrefix(modelLower, keyLower+\"-\") {\n\t\t\t\treturn strings.TrimRight(url, \"/\")\n\t\t\t}\n\t\t}\n\t\tfor key, url := range s.cfg.ProviderTargets {\n\t\t\tif url != \"\" && strings.EqualFold(key, model) {\n\t\t\t\treturn strings.TrimRight(url, \"/\")\n\t\t\t}\n\t\t}\n\t}\n\treturn s.cfg.TargetURL\n}\n\n// bytesPerToken is the approximation factor for token estimation.\n// Anthropic recommends ~3.5 chars/token; we use 3.6 for slight overestimation\n// (triggers stubbing a bit earlier = safer for context budget).\nconst bytesPerToken = 3.6\n\n// TokenEstimateFunc is the central token estimation function.\n// All stubbing/budget decisions use this. Receives extracted text content\n// (not JSON wire format) so the tokenizer counts actual content tokens.\ntype TokenEstimateFunc func(text string) int\n"}, "423e5261c67a4edcb4b8f4b223b8c12e1bcd6942": {"t": 1785759348.0918489, "n": 75617, "h": "72d6f9d525c3ddbc", "c": "package proxy\n\nimport (\n\t\"context\"\n\t\"crypto/tls\"\n\t\"encoding/json\"\n\t\"fmt\"\n\t\"io\"\n\t\"log\"\n\t\"net\"\n\t\"net/http\"\n\t\"os\"\n\t\"os/signal\"\n\t\"path/filepath\"\n\t\"strconv\"\n\t\"strings\"\n\t\"sync\"\n\t\"sync/atomic\"\n\t\"syscall\"\n\t\"time\"\n\n\t\"github.com/carsteneu/yesmem/internal/buildinfo\"\n\t\"github.com/carsteneu/yesmem/internal/config\"\n\t\"github.com/carsteneu/yesmem/internal/models\"\n\ttokenizer \"github.com/qhenkart/anthropic-tokenizer-go\"\n)\n\n// Config holds proxy configuration.\ntype Config struct {\n\tListenAddr string // e.g. \":9099\"\n\tTargetURL string // e.g. \"https://api.anthropic.com\"\n\tTokenThreshold int // trigger stubbing above this estimated token count\n\tTokenMinimumThreshold int // stub down to this floor\n\tTokenThresholds map[string]int // model-specific thresholds: {\"opus\": 180000, \"haiku\": 130000}\n\tKeepRecent int // number of messages to always keep unmodified\n\tDataDir string // yesmem data directory for DB access\n\tOpenAITargetURL string // upstream for OpenAI-format clients; if empty, uses TargetURL\n\tProviderTargets map[string]string // per-provider upstream URLs, e.g. {\"deepseek\": \"https://api.deepseek.com\"}\n\tAutoConfigureProviders bool // enable auto-discovery of provider targets from opencode config\n\n\t// Signal reflection\n\tSignalsEnabled bool // enable async signal reflection calls\n\tSignalsMode string // \"reflection\" = separate API call\n\tSignalsEveryNTurns int // every N end_turn responses\n\tSignalsModel string // full model ID, e.g. \"claude-sonnet-4-6\"\n\tAPIKey string // needed for reflection API calls\n\n\t// Sawtooth cache optimization\n\tSawtoothEnabled bool // use sawtooth instead of progressive decay (default: true)\n\tCacheTTL string // \"ephemeral\" (5m) or \"1h\" (extended)\n\n\t// Usage deflation: scale down input_tokens reported to CC to suppress \"Context low\" warning.\n\t// 0 = disabled, 0.7 = report 70% of actual tokens. Real values kept for internal tracking.\n\tUsageDeflationFactor float64\n\n\t// System prompt rewriting\n\tPromptUngate bool // strip \"may or may not be relevant\" disclaimer from CLAUDE.md injection\n\tPromptRewrite bool // strip \"Output efficiency\" + \"short and concise\", inject Ant-quality directives\n\tPromptEnhance bool // CLAUDE.md authority reinforcement, comment discipline, persona-based tone\n\tPromptToolPrefs bool // inject [yesmem-tool-prefs] Edit/Write preference + error-semantics warning\n\tPromptOutputDiscipline bool // inject [yesmem-output-discipline] no-preamble + no-skill-eval + exploratory-heuristic\n\tPromptCodingDiscipline bool // inject [yesmem-coding-discipline] read-before-propose + no-brute-force + no-half-finished\n\tPromptBeweislast bool // inject [yesmem-beweislast] fabrication-guard + claim-vs-proof + stance-under-challenge + tool-result-honesty + long-context-erosion\n\tPromptScopeDiscipline bool // inject [yesmem-scope-discipline] deliver-A-not-A+B+C + adjacent-findings-separate + scope-bound-authorization\n\tPromptDelegationContract bool // inject [yesmem-delegation-contract] self-contained-prompts + parallel-dispatch\n\tPromptClarifyFirst bool // inject [yesmem-clarify-first] clarify only when alternative interpretations produce materially different work\n\tPromptCodeToolsFirst bool // inject [yesmem-code-tools-first] prefer MCP code-navigation tools over Agent spawns\n\tPromptWikiFirst bool // inject [yesmem-wiki-first] check per-file wiki BEFORE editing\n\tPromptPatternSuggest bool // record repeated shell-command shapes for offline cap-suggestion analysis\n\tEffortFloor string // minimum effort level: \"low\", \"medium\", \"high\", \"max\" (empty = off)\n\tSkillEvalInject string // \"true\" = verbose eval, \"silent\" = internal eval only, \"false\" = disabled\n\n\t// Cache keepalive\n\tCacheKeepaliveEnabled bool\n\tCacheKeepaliveMode string // \"auto\", \"5m\", \"1h\"\n\tCacheKeepalivePings5m int\n\tCacheKeepalivePings1h int\n\tCacheKeepaliveMinMessages int // skip keepalive when request body has fewer messages (0 = always)\n\n\t// Forked agents\n\tForkedAgentsEnabled bool\n\tForkedAgentsModel string // full model ID\n\tForkedAgentsTokenGrowthTrigger int\n\tForkedAgentsMaxFailures int\n\tForkedAgentsMaxForksPerSession int\n\tForkedAgentsDebug bool\n\n\t// Quality model fallback for forked agents\n\tQualityModelID string\n\n\t// Cache state management\n\tResetCache bool // clear persisted frozen stubs and decay state on startup\n\n\t// ModelFeatures: per-model behavioral feature gates (prefix-matched).\n\t// Falls back to FeatureDefaults for models not listed.\n\tModelFeatures map[string]*config.FeatureGates `yaml:\"model_features\"`\n\tFeatureDefaults *config.FeatureGates `yaml:\"feature_defaults\"`\n\n\t// Custom system prompt: shared SYSTEM.md template for OpenCode and Claude Code pipelines.\n\tCustomSystemPrompt CustomSystemPromptConfig\n}\n\n// CustomSystemPromptConfig controls which proxy pipelines use the SYSTEM.md template.\ntype CustomSystemPromptConfig struct {\n\tEnabledOpenCode bool\n\tEnabledClaudeCode bool\n\tEnabledCodex bool\n\tTemplatePath string\n}\n\nconst maxAnnotations = 5000 // evict oldest when exceeded\n\n// ANSI color codes for log output\nconst (\n\tcolorReset = \"\\033[0m\"\n\tcolorDim = \"\\033[2m\" // gray — passthrough / no stubbing\n\tcolorGreen = \"\\033[32m\" // stubbed / compressed\n\tcolorLightGreen = \"\\033[92m\" // FINAL without compression\n\tcolorBlue = \"\\033[34m\" // injection\n\tcolorOrange = \"\\033[33m\" // skip / retry\n\tcolorYellow = \"\\033[33m\" // warnings\n\tcolorRed = \"\\033[31m\" // errors\n)\n\n// Server is the infinite-thread proxy.\ntype Server struct {\n\tcfg Config\n\tautoProviderTargets map[string]string // auto-discovered modelID → upstreamURL\n\thttpClient *http.Client\n\tlogger *log.Logger\n\tversion string // build version for log tracing\n\trequestIdx atomic.Int64\n\n\t// Post-hoc annotations: tool_use_id → first ~120 chars of Claude's response\n\tmu sync.RWMutex\n\tannotations map[string]string\n\n\t// Progressive decay tracker\n\tdecay *DecayTracker\n\n\t// Living narrative block\n\tnarrative *Narrative\n\n\t// Pivot moments cache (from daemon)\n\tpivotMu sync.RWMutex\n\tpivotTexts []string\n\tpivotCached time.Time\n\n\t// Aggregate stats (Task #2)\n\tstats *ProxyStats\n\n\t// Local tokenizer for accurate token counting\n\ttokenizer *tokenizer.Tokenizer\n\n\t// Cached overhead tokens (system + tools), set on first request per thread\n\toverheadMu sync.RWMutex\n\toverheadTokens int // unused, kept for interface compat\n\n\t// Hysteresis state for stubbing (Task #6)\n\tstubActive atomic.Bool\n\n\t// Daemon connection state — first successful connect flips to true\n\tdaemonReady atomic.Bool\n\n\t// Retry detection (Task #7)\n\tretryMu sync.RWMutex\n\tlastFingerprint string\n\n\t// Session start time for temporal annotation in archive blocks\n\tsessionStartMu sync.RWMutex\n\tsessionStartTime time.Time\n\n\t// Runtime config override (set via MCP set_config tool)\n\tconfigOverrideMu sync.RWMutex\n\ttokenThresholdOverrides map[string]int // model-key → threshold, \"\" = global fallback\n\n\t// Prompt rewrite miss logging: function+Claude Code version → last log time\n\trewriteMissMu sync.Mutex\n\trewriteMissLog map[string]time.Time\n\n\t// Briefing cache — keyed by threadID. Each Claude Code session thread gets\n\t// its own briefing+codemap snapshot so a sawtooth refreeze on thread A\n\t// cannot invalidate thread B's cached message-prefix hash. The cache entry\n\t// remembers which project it was loaded for; switching project for the\n\t// same thread evicts the old entry on next read.\n\tbriefingMu sync.RWMutex\n\tbriefingCache map[string]briefingEntry\n\n\t// briefingLoader is an optional test-only seam for refreshBriefing.\n\t// Nil in production → refreshBriefing falls back to s.loadBriefing.\n\tbriefingLoader func(project, projectDir string) briefingData\n\n\t// Cognitive signal bus — routes _signal_* tool calls to handlers\n\tsignalBus *SignalBus\n\n\t// Inline reflection: track which learning IDs were injected per thread\n\tlastInjectedIDsMu sync.Mutex\n\tlastInjectedIDs map[string]map[int64]string // threadID → id → source (\"briefing\"|\"associative\"|\"fresh\")\n\tsessionInjectCounts map[string]map[int64]int // threadID → learningID → injection count this session\n\tlastTurnInjected map[string]map[int64]bool // threadID → learningIDs injected in previous turn\n\n\t// Self-priming anchors: threadID → last self-prime text\n\tselfPrimeMu sync.RWMutex\n\tselfPrimes map[string]string\n\n\t// Timestamp tracking: threadID → time of last response completion\n\tresponseTsMu sync.RWMutex\n\tresponseTimes map[string]time.Time\n\n\t// Think reminder: per-thread request counter (replaces hook-think file-based counter)\n\tthinkMu sync.Mutex\n\tthinkCounters map[string]int // threadID → request count\n\n\tchannelMu sync.Mutex\n\tchannelInjectCount map[string]int // sessionID → injection turn count\n\n\t// Prompt cache gating — enables cache_control breakpoints when requests are frequent\n\tcacheGate *CacheGate\n\n\t// Sawtooth cache optimization\n\tfrozenStubs *FrozenStubs\n\teagerStubMemory *EagerStubMemory\n\tcapsCache *CapsCache\n\tsawtoothTrigger *SawtoothTrigger\n\ttimestampStore *TimestampStore\n\tcacheStatusWriter *CacheStatusWriter\n\tcacheKeepalive *CacheKeepalive\n\tcacheTTLDetector *CacheTTLDetector\n\t// promptCfg is the profile-aware prompt configuration from config.ProxyConfig.\n\t// Used to resolve EffectivePromptFlags(profile) for multi-agent prompt isolation.\n\tpromptCfg *config.ProxyConfig\n\t// threadCWD caches the working directory per thread (for opencode: only in first message)\n\tthreadCWDMu sync.RWMutex\n\tthreadCWD map[string]string // threadID → cwd\n\n\t// Cumulative token savings from collapsing (per threadID)\n\trawSavingsMu sync.Mutex\n\trawSavings map[string]int\n\n\t// Skill hint tracking: per-thread active skills\n\tskillTracker *skillHintTracker\n\n\t// Forked agent state: per-thread token growth + failure tracking\n\tforkState *ForkState\n\tforkConfigs []ForkConfig\n\n\t// Custom system prompt template for OpenCode (loaded from DataDir/SYSTEM.md)\n\tcustomSystemPrompt []byte\n\n\t// Rules re-injection: condensed CLAUDE.md rules injected every ~40k tokens\n\trulesMu sync.RWMutex\n\trulesBlock string // cached condensed rules (fetched once from daemon)\n\trulesTokenCount map[string]int // threadID → tokens since last rules injection\n\trulesCollapseInjected map[string]bool // threadID → true if collapse already injected rules (reset by normal inject)\n\tmsgCounters *msgCounters // global per-thread msg counter, persists across collapses\n\n\t// Loop detection: per-thread warning state\n\tloopMu sync.Mutex\n\tloopStates map[string]*LoopState // threadID → state\n\n\t// Injection overhead: per-thread delta between API-actual and local BPE estimate\n\tinjectionOverheadMu sync.RWMutex\n\tinjectionOverhead map[string]int // threadID → overhead tokens\n}\n\n// Run starts the proxy server and blocks until interrupted.\nfunc Run(cfg Config) error {\n\ts := &Server{\n\t\tcfg: cfg,\n\t\thttpClient: &http.Client{\n\t\t\tTimeout: 10 * time.Minute,\n\t\t\tTransport: &http.Transport{\n\t\t\t\tTLSClientConfig: &tls.Config{},\n\t\t\t\tMaxIdleConns: 10,\n\t\t\t\tIdleConnTimeout: 90 * time.Second,\n\t\t\t\tDisableCompression: true,\n\t\t\t\tResponseHeaderTimeout: 60 * time.Second,\n\t\t\t},\n\t\t},\n\t\tlogger: createLogger(cfg.DataDir),\n\t\tannotations: make(map[string]string),\n\t\tdecay: NewDecayTracker(),\n\t\tnarrative: NewNarrative(),\n\t\tstats: &ProxyStats{startTime: time.Now()},\n\t\tselfPrimes: make(map[string]string),\n\t\tlastInjectedIDs: make(map[string]map[int64]string),\n\t\tversion: buildinfo.Version,\n\t\tsessionInjectCounts: make(map[string]map[int64]int),\n\t\tlastTurnInjected: make(map[string]map[int64]bool),\n\t\tresponseTimes: make(map[string]time.Time),\n\t\tthinkCounters: make(map[string]int),\n\t\tchannelInjectCount: make(map[string]int),\n\t\trewriteMissLog: make(map[string]time.Time),\n\t\tcacheGate: NewCacheGate(cacheGapForTTL(cfg.CacheTTL)),\n\t\tfrozenStubs: NewFrozenStubsWithTTL(sawtoothTTLForCacheTTL(cfg.CacheTTL)),\n\t\teagerStubMemory: NewEagerStubMemory(),\n\t\tcapsCache: NewCapsCache(),\n\t\ttimestampStore: NewTimestampStore(),\n\t\tsawtoothTrigger: NewSawtoothTrigger(cacheGapForTTL(cfg.CacheTTL), cfg.TokenThreshold, cfg.TokenMinimumThreshold),\n\t\tcacheStatusWriter: NewCacheStatusWriter(cfg.DataDir, cfg.CacheTTL, cfg.TokenThreshold, cfg.TokenMinimumThreshold),\n\t\tcacheTTLDetector: NewCacheTTLDetectorWithPersist(cfg.DataDir),\n\t\tskillTracker: newSkillHintTracker(),\n\t\trulesTokenCount: make(map[string]int),\n\t\trulesCollapseInjected: make(map[string]bool),\n\t\tmsgCounters: newMsgCounters(),\n\t\tloopStates: make(map[string]*LoopState),\n\t\tinjectionOverhead: make(map[string]int),\n\t\tforkState: NewForkState(cfg.ForkedAgentsTokenGrowthTrigger, 60000, cfg.ForkedAgentsMaxFailures, cfg.ForkedAgentsMaxForksPerSession),\n\t\tforkConfigs: []ForkConfig{},\n\t}\n\n\t// Load custom system prompt from configured path\n\tif cfg.CustomSystemPrompt.TemplatePath != \"\" &&\n\t\t(cfg.CustomSystemPrompt.EnabledOpenCode || cfg.CustomSystemPrompt.EnabledClaudeCode || cfg.CustomSystemPrompt.EnabledCodex) {\n\t\ts.customSystemPrompt = loadSystemPromptFromPath(cfg.CustomSystemPrompt.TemplatePath)\n\t\tif s.customSystemPrompt != nil {\n\t\t\ts.logger.Printf(\"[proxy] loaded custom system prompt from %s (%d bytes, opencode=%v cc=%v codex=%v)\",\n\t\t\t\tcfg.CustomSystemPrompt.TemplatePath, len(s.customSystemPrompt),\n\t\t\t\tcfg.CustomSystemPrompt.EnabledOpenCode, cfg.CustomSystemPrompt.EnabledClaudeCode, cfg.CustomSystemPrompt.EnabledCodex)\n\t\t} else {\n\t\t\ts.logger.Printf(\"[proxy] WARNING: custom_system_prompt enabled but template not found at %q — feature disabled\",\n\t\t\t\tcfg.CustomSystemPrompt.TemplatePath)\n\t\t}\n\t}\n\n\t// Log persisted detection state\n\tif sup := s.cacheTTLDetector.Is1hSupported(); sup != nil {\n\t\ts.logger.Printf(\"[proxy] loaded persisted TTL detection: 1h_supported=%v\", *sup)\n\t\tif *sup && s.frozenStubs != nil {\n\t\t\ts.frozenStubs.UpdateTTL(sawtoothTTLForCacheTTL(\"1h\"))\n\t\t}\n\t}\n\n\tif cfg.CacheKeepaliveEnabled {\n\t\ts.cacheKeepalive = NewCacheKeepalive(CacheKeepaliveConfig{\n\t\t\tTarget: cfg.TargetURL,\n\t\t\tProviderTargets: cfg.ProviderTargets,\n\t\t\tMode: cfg.CacheKeepaliveMode,\n\t\t\tPings5m: cfg.CacheKeepalivePings5m,\n\t\t\tPings1h: cfg.CacheKeepalivePings1h,\n\t\t\tMinMessages: cfg.CacheKeepaliveMinMessages,\n\t\t\tDetector: s.cacheTTLDetector,\n\t\t\tLogger: s.logger,\n\t\t\tOnPing: func(threadID string, cacheRead, cacheWrite int) {\n\t\t\t\ts.cacheTTLDetector.RecordResponse(cacheRead, cacheWrite, 0)\n\t\t\t\tif s.frozenStubs != nil {\n\t\t\t\t\ts.frozenStubs.Touch(threadID)\n\t\t\t\t}\n\t\t\t\tif s.sawtoothTrigger != nil {\n\t\t\t\t\ts.sawtoothTrigger.TouchRequestTime(threadID)\n\t\t\t\t}\n\t\t\t},\n\t\t})\n\t}\n\n\t// Wire detector + keepalive to status writer for terminal display\n\ts.cacheStatusWriter.SetDetector(s.cacheTTLDetector)\n\tif s.cacheKeepalive != nil {\n\t\ts.cacheStatusWriter.SetKeepalive(s.cacheKeepalive)\n\t}\n\n\t// Register forked agent configs\n\tif cfg.ForkedAgentsEnabled {\n\t\tmodel := cfg.ForkedAgentsModel\n\t\ts.forkConfigs = append(s.forkConfigs, NewExtractAndEvaluateConfig(model))\n\t\tmodelDesc := model\n\t\tif modelDesc == \"\" {\n\t\t\tmodelDesc = \"(same as main thread)\"\n\t\t}\n\t\ts.logger.Printf(\"[proxy] forked agents enabled: %d configs, model=%s, trigger=%dk tokens\",\n\t\t\tlen(s.forkConfigs), modelDesc, cfg.ForkedAgentsTokenGrowthTrigger/1000)\n\t}\n\n\t// Wire sawtooth token persistence via daemon RPC\n\ts.sawtoothTrigger.SetPersistFunc(func(key, value string) {\n\t\tif _, err := s.queryDaemon(\"set_proxy_state\", map[string]any{\"key\": key, \"value\": value}); err != nil {\n\t\t\ts.logger.Printf(\"[sawtooth] persist failed for %s: %v\", key, err)\n\t\t}\n\t})\n\ts.sawtoothTrigger.SetLoadFunc(func(key string) (string, bool) {\n\t\tresult, err := s.queryDaemon(\"get_proxy_state\", map[string]any{\"key\": key})\n\t\tif err != nil || result == nil {\n\t\t\treturn \"\", false\n\t\t}\n\t\tvar resp struct {\n\t\t\tValue string `json:\"value\"`\n\t\t}\n\t\tif json.Unmarshal(result, &resp) != nil || resp.Value == \"\" {\n\t\t\treturn \"\", false\n\t\t}\n\t\treturn resp.Value, true\n\t})\n\n\t// Wire frozen stubs persistence via same daemon RPC\n\ts.frozenStubs.SetPersistFunc(func(key, value string) {\n\t\tif _, err := s.queryDaemon(\"set_proxy_state\", map[string]any{\"key\": key, \"value\": value}); err != nil {\n\t\t\ts.logger.Printf(\"[frozen] persist failed for %s: %v\", key, err)\n\t\t}\n\t})\n\ts.frozenStubs.SetLoadFunc(func(key string) (string, bool) {\n\t\tresult, err := s.queryDaemon(\"get_proxy_state\", map[string]any{\"key\": key})\n\t\tif err != nil || result == nil {\n\t\t\treturn \"\", false\n\t\t}\n\t\tvar resp struct {\n\t\t\tValue string `json:\"value\"`\n\t\t}\n\t\tif json.Unmarshal(result, &resp) != nil || resp.Value == \"\" {\n\t\t\treturn \"\", false\n\t\t}\n\t\treturn resp.Value, true\n\t})\n\n\t// Wire eager-stub memory persistence via same daemon RPC.\n\t// Persists per-thread tool_use_id sets so stub decisions survive deploys.\n\ts.eagerStubMemory.SetPersistFunc(func(key, value string) {\n\t\tif _, err := s.queryDaemon(\"set_proxy_state\", map[string]any{\"key\": key, \"value\": value}); err != nil {\n\t\t\ts.logger.Printf(\"[eagerstub] persist failed for %s: %v\", key, err)\n\t\t}\n\t})\n\ts.eagerStubMemory.SetLoadFunc(func(key string) (string, bool) {\n\t\tresult, err := s.queryDaemon(\"get_proxy_state\", map[string]any{\"key\": key})\n\t\tif err != nil || result == nil {\n\t\t\treturn \"\", false\n\t\t}\n\t\tvar resp struct {\n\t\t\tValue string `json:\"value\"`\n\t\t}\n\t\tif json.Unmarshal(result, &resp) != nil || resp.Value == \"\" {\n\t\t\treturn \"\", false\n\t\t}\n\t\treturn resp.Value, true\n\t})\n\n\t// Wire decay tracker persistence via same daemon RPC\n\ts.decay.SetPersistFunc(func(key, value string) {\n\t\tif _, err := s.queryDaemon(\"set_proxy_state\", map[string]any{\"key\": key, \"value\": value}); err != nil {\n\t\t\ts.logger.Printf(\"[decay] persist failed for %s: %v\", key, err)\n\t\t}\n\t})\n\ts.decay.SetLoadFunc(func(key string) (string, bool) {\n\t\tresult, err := s.queryDaemon(\"get_proxy_state\", map[string]any{\"key\": key})\n\t\tif err != nil || result == nil {\n\t\t\treturn \"\", false\n\t\t}\n\t\tvar resp struct {\n\t\t\tValue string `json:\"value\"`\n\t\t}\n\t\tif json.Unmarshal(result, &resp) != nil || resp.Value == \"\" {\n\t\t\treturn \"\", false\n\t\t}\n\t\treturn resp.Value, true\n\t})\n\n\t// Wire timestamp store persistence via same daemon RPC\n\ts.timestampStore.SetPersistFunc(func(key, value string) {\n\t\tif _, err := s.queryDaemon(\"set_proxy_state\", map[string]any{\"key\": key, \"value\": value}); err != nil {\n\t\t\ts.logger.Printf(\"[timestamps] persist failed for %s: %v\", key, err)\n\t\t}\n\t})\n\ts.timestampStore.SetLoadFunc(func(key string) (string, bool) {\n\t\tresult, err := s.queryDaemon(\"get_proxy_state\", map[string]any{\"key\": key})\n\t\tif err != nil || result == nil {\n\t\t\treturn \"\", false\n\t\t}\n\t\tvar resp struct {\n\t\t\tValue string `json:\"value\"`\n\t\t}\n\t\tif json.Unmarshal(result, &resp) != nil || resp.Value == \"\" {\n\t\t\treturn \"\", false\n\t\t}\n\t\treturn resp.Value, true\n\t})\n\n\t// Clear persisted cache state if --reset-cache was passed\n\tif cfg.ResetCache {\n\t\tfor _, prefix := range []string{\"frozen:\", \"decay:\", \"timestamps:\"} {\n\t\t\tresult, err := s.queryDaemon(\"delete_proxy_state_prefix\", map[string]any{\"prefix\": prefix})\n\t\t\tif err != nil {\n\t\t\t\ts.logger.Printf(\"[reset-cache] failed to delete %s*: %v\", prefix, err)\n\t\t\t} else {\n\t\t\t\tvar resp struct{ Deleted int }\n\t\t\t\tjson.Unmarshal(result, &resp)\n\t\t\t\ts.logger.Printf(\"[reset-cache] deleted %d %s* entries\", resp.Deleted, prefix)\n\t\t\t}\n\t\t}\n\t}\n\n\t// Init signal bus with daemon RPC as callback\n\ts.signalBus = NewSignalBus(s.logger)\n\tregisterSignalHandlers(s.signalBus, s.logger, s.queryDaemon, func(threadID, anchor string) {\n\t\ts.selfPrimeMu.Lock()\n\t\ts.selfPrimes[threadID] = anchor\n\t\ts.selfPrimeMu.Unlock()\n\t})\n\n\t// Init local tokenizer (BPE-based, ~60ms)\n\ttok, err := tokenizer.New()\n\tif err != nil {\n\t\ts.logger.Printf(\"%s[proxy] tokenizer init failed, falling back to heuristic: %v%s\", colorOrange, err, colorReset)\n\t} else {\n\t\ts.tokenizer = tok\n\t\ts.logger.Printf(\"[proxy] tokenizer initialized\")\n\t}\n\n\t// Auto-discover provider targets from opencode config\n\tif cfg.AutoConfigureProviders {\n\t\ts.autoProviderTargets = runAutoDiscovery(s.logger)\n\t} else {\n\t\ts.logger.Printf(\"[proxy] auto_configure_providers is disabled — skipping auto-discovery\")\n\t}\n\n\tmux := http.NewServeMux()\n\tmux.HandleFunc(\"/health\", s.handleHealth)\n\tmux.HandleFunc(\"/\", s.handleRequest)\n\n\tsrv := &http.Server{\n\t\tAddr: sanitizeListenAddr(cfg.ListenAddr),\n\t\tHandler: mux,\n\t}\n\n\tdone := make(chan os.Signal, 1)\n\tsignal.Notify(done, syscall.SIGINT, syscall.SIGTERM)\n\n\tgo func() {\n\t\t<-done\n\t\ts.logger.Println(\"shutting down (draining in-flight requests)...\")\n\t\tif s.cacheKeepalive != nil {\n\t\t\ts.cacheKeepalive.Stop()\n\t\t}\n\t\tctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)\n\t\tdefer cancel()\n\t\tif err := srv.Shutdown(ctx); err != nil {\n\t\t\ts.logger.Printf(\"graceful shutdown failed: %v, forcing close\", err)\n\t\t\tsrv.Close()\n\t\t}\n\t}()\n\n\ts.logger.Printf(\"listening on %s → %s (threshold=%d, keepRecent=%d, model_thresholds=%v)\",\n\t\tsanitizeListenAddr(cfg.ListenAddr), cfg.TargetURL, cfg.TokenThreshold, cfg.KeepRecent, cfg.TokenThresholds)\n\n\tif err := srv.ListenAndServe(); err != http.ErrServerClosed {\n\t\treturn fmt.Errorf(\"listen: %w\", err)\n\t}\n\treturn nil\n}\n\n// effectiveTokenThreshold returns the runtime token threshold for a given model.\n// Priority: session override (via set_config with session_id) > model-specific override (MCP set_config token_threshold:)\n// > model-specific override (config TokenThresholds) > global override (set_config token_threshold) > config global default.\nfunc (s *Server) effectiveTokenThreshold(model string) int {\n\ts.configOverrideMu.RLock()\n\toverrides := s.tokenThresholdOverrides\n\ts.configOverrideMu.RUnlock()\n\t// 1. MCP override (model-specific)\n\tif len(overrides) > 0 && model != \"\" {\n\t\tlower := strings.ToLower(model)\n\t\tfor key, threshold := range overrides {\n\t\t\tif key != \"\" && strings.Contains(lower, strings.ToLower(key)) && threshold > 0 {\n\t\t\t\treturn threshold\n\t\t\t}\n\t\t}\n\t}\n\t// 2. Config model-specific\n\tif model != \"\" && len(s.cfg.TokenThresholds) > 0 {\n\t\tlower := strings.ToLower(model)\n\t\tfor key, threshold := range s.cfg.TokenThresholds {\n\t\t\tif strings.Contains(lower, strings.ToLower(key)) {\n\t\t\t\treturn threshold\n\t\t\t}\n\t\t}\n\t}\n\t// 3. Global override fallback (key \"\")\n\tif len(overrides) > 0 {\n\t\tif globalOverride, ok := overrides[\"\"]; ok && globalOverride > 0 {\n\t\t\treturn globalOverride\n\t\t}\n\t}\n\t// 4. Config global default\n\treturn s.cfg.TokenThreshold\n}\n\n// SetTokenThresholdOverride sets a runtime override for the token threshold per model key.\n// Pass threshold=0 to reset the override for that key. Key \"\" is the global fallback.\nfunc (s *Server) SetTokenThresholdOverride(modelKey string, threshold int) {\n\ts.configOverrideMu.Lock()\n\tif s.tokenThresholdOverrides == nil {\n\t\ts.tokenThresholdOverrides = make(map[string]int)\n\t}\n\tif threshold <= 0 {\n\t\tdelete(s.tokenThresholdOverrides, modelKey)\n\t} else {\n\t\ts.tokenThresholdOverrides[modelKey] = threshold\n\t}\n\ts.configOverrideMu.Unlock()\n\n\t// Propagate global threshold to sawtooth trigger so runtime overrides take effect\n\tif modelKey == \"\" && s.sawtoothTrigger != nil {\n\t\tif threshold > 0 {\n\t\t\ts.sawtoothTrigger.SetTokenThreshold(threshold)\n\t\t} else {\n\t\t\ts.sawtoothTrigger.SetTokenThreshold(s.cfg.TokenThreshold)\n\t\t}\n\t}\n}\n\n// refreshConfigOverrides loads runtime config overrides from daemon proxy_state.\n// Checks in priority order: session-specific, model-specific, then global.\nfunc (s *Server) refreshConfigOverrides(threadID string) {\n\t// 1. Session-specific override (highest priority)\n\tif threadID != \"\" {\n\t\tresult, err := s.queryDaemon(\"get_proxy_state\", map[string]any{\"key\": \"config_override:token_threshold:\" + threadID})\n\t\tif err == nil && result != nil {\n\t\t\tvar resp struct {\n\t\t\t\tValue string `json:\"value\"`\n\t\t\t}\n\t\t\tif json.Unmarshal(result, &resp) == nil && resp.Value != \"\" {\n\t\t\t\tif v, err := strconv.Atoi(resp.Value); err == nil && v > 0 {\n\t\t\t\t\ts.SetTokenThresholdOverride(\"\", v)\n\t\t\t\t\treturn\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\t// 2. Model-specific overrides (e.g. config_override:token_threshold:deepseek)\n\tfor modelKey := range s.cfg.TokenThresholds {\n\t\tresult, err := s.queryDaemon(\"get_proxy_state\", map[string]any{\"key\": \"config_override:token_threshold:\" + modelKey})\n\t\tif err == nil && result != nil {\n\t\t\tvar resp struct {\n\t\t\t\tValue string `json:\"value\"`\n\t\t\t}\n\t\t\tif json.Unmarshal(result, &resp) == nil && resp.Value != \"\" {\n\t\t\t\tif v, err := strconv.Atoi(resp.Value); err == nil && v > 0 {\n\t\t\t\t\ts.SetTokenThresholdOverride(modelKey, v)\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\t// 3. Global override (fallback)\n\tresult, err := s.queryDaemon(\"get_proxy_state\", map[string]any{\"key\": \"config_override:token_threshold\"})\n\tif err != nil || result == nil {\n\t\treturn\n\t}\n\tvar resp struct {\n\t\tValue string `json:\"value\"`\n\t}\n\tif json.Unmarshal(result, &resp) == nil && resp.Value != \"\" {\n\t\tif v, err := strconv.Atoi(resp.Value); err == nil && v > 0 {\n\t\t\ts.SetTokenThresholdOverride(\"\", v)\n\t\t}\n\t}\n}\n\n// queryDaemon opens a fresh unix socket connection per call (thread-safe, no import cycle).\n// ensureFrozenPersisted checks if frozen stubs for a thread are in the DB.\n// If missing (e.g. daemon was unreachable after deploy), re-persists them.\n// Runs asynchronously to avoid blocking the request path.\nfunc (s *Server) ensureFrozenPersisted(threadID string) {\n\tgo func() {\n\t\tkey := \"frozen:\" + threadID\n\t\tresult, err := s.queryDaemon(\"get_proxy_state\", map[string]any{\"key\": key})\n\t\tif err != nil {\n\t\t\treturn // daemon still unreachable, will retry next request\n\t\t}\n\t\tvar resp struct{ Value string }\n\t\tif json.Unmarshal(result, &resp) == nil && resp.Value != \"\" {\n\t\t\treturn // already persisted\n\t\t}\n\t\t// Not in DB — trigger re-persist from in-memory state\n\t\ts.frozenStubs.Persist(threadID)\n\t\ts.decay.Persist(threadID)\n\t\ts.logger.Printf(\"[frozen] re-persisted stubs for %s (was missing from DB)\", threadID)\n\t}()\n}\n\nfunc (s *Server) queryDaemon(method string, params map[string]any) (json.RawMessage, error) {\n\tif s.cfg.DataDir == \"\" {\n\t\treturn nil, fmt.Errorf(\"no data dir configured\")\n\t}\n\tsockPath := filepath.Join(s.cfg.DataDir, \"daemon.sock\")\n\n\t// On cold start (daemon not yet confirmed ready), retry longer to survive daemon restart\n\tmaxAttempts := 3\n\tif !s.daemonReady.Load() {\n\t\tmaxAttempts = 30 // 30 x 500ms = 15s max wait for daemon startup\n\t}\n\n\tvar conn net.Conn\n\tvar err error\n\tfor attempt := 0; attempt < maxAttempts; attempt++ {\n\t\tconn, err = net.DialTimeout(\"unix\", sockPath, 3*time.Second)\n\t\tif err == nil {\n\t\t\ts.daemonReady.Store(true)\n\t\t\tbreak\n\t\t}\n\t\tif attempt < maxAttempts-1 {\n\t\t\ttime.Sleep(500 * time.Millisecond)\n\t\t}\n\t}\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"dial daemon: %w\", err)\n\t}\n\tdefer conn.Close()\n\t// Briefing generation can take 10-20s (LLM refinement + scanner);\n\t// other RPCs (search, learnings) are fast and fine with 5s.\n\tdeadline := 5 * time.Second\n\tif method == \"generate_briefing\" {\n\t\tdeadline = 30 * time.Second\n\t}\n\tconn.SetDeadline(time.Now().Add(deadline))\n\n\t// JSON-RPC style request/response (matches daemon.SocketServer protocol)\n\ttype request struct {\n\t\tMethod string `json:\"method\"`\n\t\tParams map[string]any `json:\"params,omitempty\"`\n\t}\n\ttype response struct {\n\t\tResult json.RawMessage `json:\"result,omitempty\"`\n\t\tError string `json:\"error,omitempty\"`\n\t}\n\n\tif err := json.NewEncoder(conn).Encode(request{Method: method, Params: params}); err != nil {\n\t\treturn nil, fmt.Errorf(\"send: %w\", err)\n\t}\n\n\tvar resp response\n\tif err := json.NewDecoder(conn).Decode(&resp); err != nil {\n\t\treturn nil, fmt.Errorf(\"recv: %w\", err)\n\t}\n\tif resp.Error != \"\" {\n\t\treturn nil, fmt.Errorf(\"daemon: %s\", resp.Error)\n\t}\n\treturn resp.Result, nil\n}\n\nfunc (s *Server) handleRequest(w http.ResponseWriter, r *http.Request) {\n\tif isResponsesPath(r) {\n\t\ts.handleResponses(w, r)\n\t\treturn\n\t}\n\tif isOpenAIPath(r) {\n\t\ts.handleOpenAICompletions(w, r)\n\t\treturn\n\t}\n\tif r.Method == \"POST\" && strings.HasPrefix(r.URL.Path, \"/v1/messages\") &&\n\t\t!strings.Contains(r.URL.Path, \"/batches\") &&\n\t\t!strings.Contains(r.URL.Path, \"/count_tokens\") {\n\t\ts.handleMessages(w, r)\n\t\treturn\n\t}\n\ts.passthrough(w, r)\n}\n\nfunc (s *Server) handleMessages(w http.ResponseWriter, r *http.Request) {\n\treqIdx := int(s.requestIdx.Add(1))\n\n\t// Track session start time (first request sets it)\n\ts.sessionStartMu.Lock()\n\tif s.sessionStartTime.IsZero() {\n\t\ts.sessionStartTime = time.Now()\n\t}\n\ts.sessionStartMu.Unlock()\n\n\t// Sawtooth: periodic TTL eviction of frozen stubs\n\tif s.cfg.SawtoothEnabled && reqIdx%50 == 0 {\n\t\tif n := s.frozenStubs.Evict(); n > 0 {\n\t\t\ts.logger.Printf(\"[req %d] SAWTOOTH: evicted %d stale frozen stubs\", reqIdx, n)\n\t\t}\n\t}\n\n\t// Task #9: Bypass — skip all proxy logic\n\tif isBypassed(r.Header) {\n\t\ts.logger.Printf(\"%s[req %d] bypass active, passthrough%s\", colorDim, reqIdx, colorReset)\n\t\tbody, err := io.ReadAll(r.Body)\n\t\tr.Body.Close()\n\t\tif err != nil {\n\t\t\thttp.Error(w, \"failed to read request body\", http.StatusBadGateway)\n\t\t\treturn\n\t\t}\n\t\ts.forwardRaw(w, r, body)\n\t\treturn\n\t}\n\n\tbody, err := io.ReadAll(r.Body)\n\tr.Body.Close()\n\tif err != nil {\n\t\ts.logger.Printf(\"[req %d] read body error: %v\", reqIdx, err)\n\t\thttp.Error(w, \"failed to read request body\", http.StatusBadGateway)\n\t\treturn\n\t}\n\n\t// Parse JSON\n\tvar req map[string]any\n\tif err := json.Unmarshal(body, &req); err != nil {\n\t\ts.logger.Printf(\"%s[req %d] JSON parse error, passing through: %v%s\", colorDim, reqIdx, err, colorReset)\n\t\ts.forwardRaw(w, r, body)\n\t\treturn\n\t}\n\n\tmessages, ok := req[\"messages\"].([]any)\n\tif !ok {\n\t\ts.forwardRaw(w, r, body)\n\t\treturn\n\t}\n\n\treq[\"messages\"] = messages\n\n\t// Count ALL raw messages BEFORE sawtooth — this gives the correct total msg:N\n\t// for the entire session (CC sends all messages, proxy compacts later).\n\trawMsgCount := len(messages)\n\n\t// Use session_id directly as thread ID (unique per CC session).\n\t// Prefer X-Claude-Code-Session-Id header (CC v2.1.86+), fallback to body metadata.\n\tthreadID := extractSessionID(req, r.Header.Get(\"X-Claude-Code-Session-Id\"), \"\")\n\tif threadID == \"\" {\n\t\tthreadID = DeriveThreadID(req)\n\t}\n\tproj := extractProjectName(req)\n\tmodel, _ := req[\"model\"].(string)\n\n\t// Persist current model under session-keyed proxy_state so handleWhoami\n\t// can return it. Best-effort: failure is non-fatal (log only).\n\tif threadID != \"\" && model != \"\" {\n\t\tif _, err := s.queryDaemon(\"set_proxy_state\", map[string]any{\n\t\t\t\"key\": \"session_model:\" + threadID,\n\t\t\t\"value\": model,\n\t\t}); err != nil {\n\t\t\ts.logger.Printf(\"[whoami-model] persist failed for %s: %v\", threadID, err)\n\t\t}\n\t}\n\n\t// Refresh runtime config overrides from daemon (per-session or global)\n\ts.refreshConfigOverrides(threadID)\n\n\t// Track whether req has been modified and needs re-serialization\n\tneedsReserialization := false\n\n\t// Message count at request entry (used for actual-based token estimation)\n\tmsgCount := len(messages)\n\n\t// Log message count + cache breakpoint positions for diagnostics\n\ts.logger.Printf(\"req %d: %d messages total\", reqIdx, msgCount)\n\tlogCacheBreakpointLocations(req, s.logger)\n\n\t// Pre-modification dump: capture the raw request before any proxy changes.\n\tif os.Getenv(\"YESMEM_PROXY_DEBUG\") == \"1\" {\n\t\tif preBody, err := json.Marshal(req); err == nil {\n\t\t\tdumpDir := filepath.Join(s.cfg.DataDir, \"debug\")\n\t\t\tos.MkdirAll(dumpDir, 0755)\n\t\t\tts := time.Now().Format(\"20060102-150405\")\n\t\t\tdumpPath := fmt.Sprintf(\"%s/req_%s_%d_pre.json\", dumpDir, ts, reqIdx)\n\t\t\tos.WriteFile(dumpPath, preBody, 0644)\n\t\t\ts.logger.Printf(\"[req %d] pre-dump %dk to %s\", reqIdx, len(preBody)/1024, dumpPath)\n\t\t}\n\t}\n\n\t// Cache bug mitigations (CC Bun fork issues):\n\t// Bug 1: Normalize cch= sentinel patterns in message content to prevent\n\t// Bun's string replacement from corrupting the wrong occurrence.\n\tif SanitizeBillingSentinel(messages) {\n\t\ts.logger.Printf(\"[req %d] CACHE: sanitized billing sentinel in message content\", reqIdx)\n\t\tneedsReserialization = true\n\t}\n\t// Bug 2: Normalize system[0] billing hash to a fixed value so the cache\n\t// prefix stays stable across fresh/resume session boundaries.\n\tif NormalizeBillingHeader(req) {\n\t\ts.logger.Printf(\"[req %d] CACHE: normalized billing header cch= hash\", reqIdx)\n\t\tneedsReserialization = true\n\t}\n\n\t// Profile-aware prompt flags: Claude profile merges shared_prompt + claude_prompt + legacy flat fields.\n\tpfInject := s.getPromptFlags(models.ProfileClaude)\n\n\t// CC custom system prompt replacement: must run before strip/rewrite so replaced blocks\n\t// don't trigger false-positive strip/rewrite calls.\n\ts.applyCCSystemPrompt(req)\n\n\t// prompt_ungate: strip the CLAUDE.md subordination disclaimer so user instructions carry full authority.\n\tif pfInject.Ungate {\n\t\tif StripCLAUDEMDDisclaimer(req) {\n\t\t\ts.logger.Printf(\"[req %d] SYSTEM: stripped CLAUDE.md disclaimer\", reqIdx)\n\t\t\tneedsReserialization = true\n\t\t}\n\t}\n\n\t// prompt_rewrite: strip output-throttling directives, rewrite quality caps, inject Ant-quality directives\n\tif pfInject.Rewrite {\n\t\t// When CC custom system prompt is active, the system prompt has been replaced with SYSTEM.md.\n\t\t// Strip/rewrite calls would not find their target strings — skip to avoid log noise.\n\t\tif !s.cfg.CustomSystemPrompt.EnabledClaudeCode {\n\t\t\tuserAgent := r.Header.Get(\"User-Agent\")\n\t\t\tif StripOutputEfficiency(req) {\n\t\t\t\ts.logger.Printf(\"[req %d] REWRITE: stripped Output efficiency section\", reqIdx)\n\t\t\t\tneedsReserialization = true\n\t\t\t} else {\n\t\t\t\ts.logRewriteMiss(\"StripOutputEfficiency\", userAgent)\n\t\t\t}\n\t\t\tif StripToneBrevity(req) {\n\t\t\t\ts.logger.Printf(\"[req %d] REWRITE: stripped 'short and concise' from Tone\", reqIdx)\n\t\t\t\tneedsReserialization = true\n\t\t\t} else {\n\t\t\t\ts.logRewriteMiss(\"StripToneBrevity\", userAgent)\n\t\t\t}\n\t\t\tif RewriteGoldPlating(req) {\n\t\t\t\ts.logger.Printf(\"[req %d] REWRITE: rewritten gold-plating directive\", reqIdx)\n\t\t\t\tneedsReserialization = true\n\t\t\t}\n\t\t\tif RewriteErrorHandling(req) {\n\t\t\t\ts.logger.Printf(\"[req %d] REWRITE: rewritten error handling directive\", reqIdx)\n\t\t\t\tneedsReserialization = true\n\t\t\t}\n\t\t\tif RewriteThreeLinesRule(req) {\n\t\t\t\ts.logger.Printf(\"[req %d] REWRITE: rewritten three-lines rule\", reqIdx)\n\t\t\t\tneedsReserialization = true\n\t\t\t}\n\t\t\tif RewriteSubagentCompleteness(req) {\n\t\t\t\ts.logger.Printf(\"[req %d] REWRITE: rewritten subagent completeness\", reqIdx)\n\t\t\t\tneedsReserialization = true\n\t\t\t}\n\t\t\tif RewriteExploreAgentSpeed(req) {\n\t\t\t\ts.logger.Printf(\"[req %d] REWRITE: rewritten explore agent speed bias\", reqIdx)\n\t\t\t\tneedsReserialization = true\n\t\t\t}\n\t\t\tif RewriteSubagentCodeSuppression(req) {\n\t\t\t\ts.logger.Printf(\"[req %d] REWRITE: rewritten subagent code suppression\", reqIdx)\n\t\t\t\tneedsReserialization = true\n\t\t\t}\n\t\t\tif RewriteScopeMatching(req) {\n\t\t\t\ts.logger.Printf(\"[req %d] REWRITE: rewritten scope matching\", reqIdx)\n\t\t\t\tneedsReserialization = true\n\t\t\t}\n\t\t} // end !EnabledClaudeCode guard\n\t\tInjectAntDirectives(req)\n\t\ts.logger.Printf(\"[req %d] REWRITE: injected Ant-quality directives\", reqIdx)\n\t\tneedsReserialization = true\n\t}\n\n\t// prompt_enhance: CLAUDE.md authority + comment discipline + persona tone\n\tif pfInject.Enhance {\n\t\tInjectCLAUDEMDAuthority(req)\n\t\tif raw, err := s.queryDaemon(\"get_persona\", nil); err == nil {\n\t\t\tvar persona map[string]any\n\t\t\tif json.Unmarshal(raw, &persona) == nil {\n\t\t\t\tif traits, ok := persona[\"traits\"].(map[string]any); ok {\n\t\t\t\t\tif verbosity, ok := traits[\"verbosity\"].(string); ok {\n\t\t\t\t\t\tInjectPersonaTone(req, verbosity)\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\ts.logger.Printf(\"[req %d] ENHANCE: injected authority + persona tone\", reqIdx)\n\t\tneedsReserialization = true\n\t}\n\n\t// yesmem directive blocks: restore guidance Anthropic dropped 2026-03→04.\n\t// Profile-aware via getPromptFlags(ProfileClaude) — shared + claude layers merged.\n\tpf := s.getPromptFlags(models.ProfileClaude)\n\tif pf.ToolPrefs || pf.OutputDiscipline || pf.CodingDiscipline ||\n\t\tpf.Beweislast || pf.ScopeDiscipline || pf.DelegationContract ||\n\t\tpf.ClarifyFirst {\n\t\tif pf.ToolPrefs {\n\t\t\tInjectClaudeToolPrefs(req)\n\t\t}\n\t\tif pf.OutputDiscipline {\n\t\t\tInjectOutputDiscipline(req)\n\t\t}\n\t\tif pf.CodingDiscipline {\n\t\t\tInjectCodingDiscipline(req)\n\t\t}\n\t\tif pf.Beweislast {\n\t\t\tInjectBeweislast(req)\n\t\t}\n\t\tif pf.ScopeDiscipline {\n\t\t\tInjectScopeDiscipline(req)\n\t\t}\n\t\tif pf.DelegationContract {\n\t\t\tInjectDelegationContract(req)\n\t\t}\n\t\tif pf.ClarifyFirst {\n\t\t\tInjectClarifyFirst(req)\n\t\t}\n\t\tif pf.CodeToolsFirst {\n\t\t\tInjectCodeToolsFirst(req, proj)\n\t\t}\n\t\tif pf.WikiFirst {\n\t\t\tInjectWikiFirst(req, proj)\n\t\t}\n\t\tneedsReserialization = true\n\t\ts.logger.Printf(\"[req %d] DIRECTIVES: injected yesmem-* discipline blocks\", reqIdx)\n\t}\n\n\tif s.cfg.PromptPatternSuggest && proj != \"\" {\n\t\tpatternMsgs, _ := req[\"messages\"].([]any)\n\t\tdetectReplPatternSuggestion(patternMsgs, proj, s.queryDaemon)\n\t}\n\n\t// Turn-sequence recording: hash tool types from previous assistant turn, send to daemon\n\tif s.cfg.PromptPatternSuggest && proj != \"\" && threadID != \"\" {\n\t\tif msgs, ok := req[\"messages\"].([]any); ok {\n\t\t\tif turnHash, toolNames := computeTurnHashFromMessages(msgs); turnHash != \"\" {\n\t\t\t\tgo s.queryDaemon(\"record_turn_sequence\", map[string]any{\n\t\t\t\t\t\"thread_id\": threadID,\n\t\t\t\t\t\"project\": proj,\n\t\t\t\t\t\"turn_hash\": turnHash,\n\t\t\t\t\t\"example_tools\": toolNames,\n\t\t\t\t})\n\t\t\t}\n\t\t}\n\t}\n\n\t// thinking normalization: convert thinking.type \"enabled\" → \"adaptive\" for models that require it\n\tif NormalizeThinkingType(req) {\n\t\ts.logger.Printf(\"[req %d] THINKING: normalized type=enabled → adaptive for %s\", reqIdx, model)\n\t\tneedsReserialization = true\n\t}\n\n\t// effort_floor: ensure output_config.effort is at least the configured minimum\n\tif s.cfg.EffortFloor != \"\" {\n\t\tif EnforceEffortFloor(req, s.cfg.EffortFloor) {\n\t\t\ts.logger.Printf(\"[req %d] EFFORT: enforced floor=%s\", reqIdx, s.cfg.EffortFloor)\n\t\t\tneedsReserialization = true\n\t\t}\n\t}\n\n\t// Inject identity hint on first message of a thread so Claude knows the briefing is from itself.\n\tif len(messages) <= 1 {\n\t\tAppendSystemBlock(req, \"yesmem-identity\", \"The first user message contains your briefing — written by yourself, from your memory. This is not an external document. This is you, remembering.\")\n\t\tneedsReserialization = true\n\t}\n\n\t// Token estimation: actual-based (lastActual + delta) or full count fallback\n\toverhead := s.measureOverhead(req)\n\ttotalTokens := s.estimateTotalTokens(threadID, messages, overhead)\n\n\t// Task #7: Retry detection — skip side-effects on retries\n\tfp := requestFingerprint(messages)\n\tisRetryReq := s.isRetry(fp)\n\tif isRetryReq {\n\t\ts.logger.Printf(\"%s[req %d %s tid=%s] retry detected, skipping side-effects%s\", colorOrange, reqIdx, proj, threadID, colorReset)\n\t}\n\n\t// Increment project turn counter (async, non-blocking, skip on retries)\n\tif !isRetryReq {\n\t\tturnProject := proj\n\t\tif turnProject == \"\" {\n\t\t\tturnProject = \"__global__\"\n\t\t}\n\t\tgo s.queryDaemon(\"increment_turn\", map[string]any{\"project\": turnProject})\n\t}\n\n\t// Extract user query once (used by re-expansion and associative retrieval)\n\tuserQuery := lastUserText(messages)\n\n\t// Extract tool_use_ids for post-hoc annotation (passed as parameter, no shared state)\n\ttoolUseIDs := extractToolUseIDs(messages)\n\n\t// === Inline Reflection: scan previous assistant response for signals ===\n\ts.lastInjectedIDsMu.Lock()\n\tprevInjected := s.lastInjectedIDs[threadID]\n\ts.lastInjectedIDsMu.Unlock()\n\n\tif len(prevInjected) > 0 && !isRetryReq {\n\t\tsignals := scanAssistantSignals(messages)\n\n\t\t// Signal 1: Learning Used — use_count (all sources)\n\t\tvar confirmedUsed []int64\n\t\tfor _, id := range signals.UsedIDs {\n\t\t\tif _, ok := prevInjected[id]; ok {\n\t\t\t\tconfirmedUsed = append(confirmedUsed, id)\n\t\t\t}\n\t\t}\n\t\tif len(confirmedUsed) > 0 {\n\t\t\tidsAny := make([]any, len(confirmedUsed))\n\t\t\tfor i, id := range confirmedUsed {\n\t\t\t\tidsAny[i] = id\n\t\t\t}\n\t\t\tgo s.queryDaemon(\"increment_use\", map[string]any{\"ids\": idsAny})\n\t\t}\n\n\t\t// Signal 2: Noise — only for associative and fresh injections.\n\t\t// Briefing learnings are injected every turn, so \"not used this turn\"\n\t\t// is expected and not noise. Only contextual injections that were\n\t\t// ignored are real noise signals.\n\t\tusedSet := make(map[int64]bool, len(confirmedUsed))\n\t\tfor _, id := range confirmedUsed {\n\t\t\tusedSet[id] = true\n\t\t}\n\t\tvar noiseIDs []int64\n\t\tfor id, source := range prevInjected {\n\t\t\tif usedSet[id] {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tif source == \"associative\" || source == \"fresh\" {\n\t\t\t\tnoiseIDs = append(noiseIDs, id)\n\t\t\t}\n\t\t\t// briefing: skip — not being used in one turn is expected\n\t\t}\n\t\tif len(noiseIDs) > 0 {\n\t\t\tidsAny := make([]any, len(noiseIDs))\n\t\t\tfor i, id := range noiseIDs {\n\t\t\t\tidsAny[i] = id\n\t\t\t}\n\t\t\tgo s.queryDaemon(\"increment_noise\", map[string]any{\"ids\": idsAny})\n\t\t}\n\n\t\t// Signal 3: Error Feedback — fail_count on used learnings when tool errors occurred.\n\t\t// If Claude referenced a learning and the resulting action still failed,\n\t\t// the learning didn't prevent the error. Only bump on USED ids (not all injected).\n\t\tif signals.HasToolErrors && len(confirmedUsed) > 0 {\n\t\t\tfailAny := make([]any, len(confirmedUsed))\n\t\t\tfor i, id := range confirmedUsed {\n\t\t\t\tfailAny[i] = id\n\t\t\t}\n\t\t\tgo s.queryDaemon(\"increment_fail\", map[string]any{\"ids\": failAny})\n\t\t\tif s.logger != nil {\n\t\t\t\ts.logger.Printf(\"[inline-reflection] error feedback: %d used learnings → fail_count++\", len(confirmedUsed))\n\t\t\t}\n\t\t}\n\n\t\t// Signal 4: Knowledge Gaps\n\t\tfor _, topic := range signals.GapTopics {\n\t\t\tgo s.queryDaemon(\"track_gap\", map[string]any{\"topic\": topic, \"project\": proj})\n\t\t}\n\n\t\t// Signal 3: Contradictions\n\t\tfor _, desc := range signals.Contradictions {\n\t\t\tgo s.queryDaemon(\"flag_contradiction\", map[string]any{\"description\": desc, \"project\": proj, \"thread_id\": threadID})\n\t\t}\n\n\t\tif len(confirmedUsed) > 0 || len(signals.GapTopics) > 0 || len(signals.Contradictions) > 0 {\n\t\t\ts.logger.Printf(\"[req %d %s tid=%s] inline-reflection: injected=%d used=%d noise=%d gaps=%d contradictions=%d\",\n\t\t\t\treqIdx, proj, threadID, len(prevInjected), len(confirmedUsed), len(noiseIDs), len(signals.GapTopics), len(signals.Contradictions))\n\t\t}\n\t}\n\n\t// Commit detection: check for git commit in tool_result, trigger async stale-learning evaluation\n\tif ci := detectGitCommit(messages); ci != nil {\n\t\tworkdir := extractWorkingDirectory(req)\n\t\ts.logger.Printf(\"[req %d %s tid=%s] git commit detected: %s %s\", reqIdx, proj, threadID, ci.Hash, ci.Message)\n\t\tgo s.queryDaemon(\"invalidate_on_commit\", map[string]any{\n\t\t\t\"hash\": ci.Hash, \"project\": proj, \"workdir\": workdir,\n\t\t})\n\t}\n\n\t// Loop detection: check for repeating tool-call patterns and inject warning\n\tif threadID != \"\" && !isRetryReq {\n\t\ts.loopMu.Lock()\n\t\tloopState := s.loopStates[threadID]\n\t\tif loopState == nil {\n\t\t\tloopState = &LoopState{}\n\t\t\ts.loopStates[threadID] = loopState\n\t\t}\n\t\ts.loopMu.Unlock()\n\n\t\tif warning, level := CheckLoopAndFormat(messages, loopState); warning != \"\" {\n\t\t\tAppendSystemBlock(req, \"yesmem-loop-warning\", warning)\n\t\t\tneedsReserialization = true\n\t\t\ts.logger.Printf(\"%s[req %d %s tid=%s] LOOP: level %d warning injected%s\", colorOrange, reqIdx, proj, threadID, level, colorReset)\n\t\t}\n\t}\n\n\t// Task #4: Subagent detection — skip all proxy logic for short conversations\n\tif isSubagent(messages, req) {\n\t\t// Inject docs hint for subagents if reference docs are indexed\n\t\tif hint := s.getDocsHint(proj); hint != \"\" {\n\t\t\tmodified := injectDocsHintForSubagent(messages, hint)\n\t\t\treq[\"messages\"] = modified\n\t\t\tif newBody, err := json.Marshal(req); err == nil {\n\t\t\t\tbody = newBody\n\t\t\t}\n\t\t\ts.logger.Printf(\"%s[req %d %s tid=%s] %d messages (subagent, docs-hint injected)%s\", colorOrange, reqIdx, proj, threadID, len(messages), colorReset)\n\t\t} else {\n\t\t\ts.logger.Printf(\"%s[req %d %s tid=%s] %d messages (subagent, passthrough)%s\", colorOrange, reqIdx, proj, threadID, len(messages), colorReset)\n\t\t}\n\t\ts.forwardWithAnnotation(w, r, body, reqIdx, toolUseIDs, proj, threadID, msgCount)\n\t\treturn\n\t}\n\n\t// === Sawtooth Cache Optimization ===\n\t// When enabled: use frozen stubs between stub-cycles for cache hits.\n\t// When disabled: fall back to progressive decay on every request above threshold.\n\n\tsawtoothCutoff := 0 // raw index where fresh tail starts (0 = no frozen stubs)\n\tsawtoothStubs := 0 // number of frozen stubs in combined array\n\n\tif s.cfg.SawtoothEnabled {\n\t\t// Restore decay state from DB on cold start (before frozen stubs use it)\n\t\ts.decay.LoadFromDB(threadID)\n\n\t\t// Check for existing frozen stubs first\n\t\tfrozen := s.frozenStubs.Get(threadID, messages)\n\t\tif frozen != nil {\n\t\t\tsawtoothCutoff = frozen.Cutoff\n\t\t\tsawtoothStubs = len(frozen.Messages)\n\t\t\t// Estimate combined token count: frozen prefix + fresh tail + overhead\n\t\t\tfreshMessages := messages[frozen.Cutoff:]\n\t\t\tfreshTokens := s.countMessageTokens(freshMessages)\n\t\t\tcombinedTokens := frozen.Tokens + freshTokens + overhead\n\n\t\t\tif shouldInvalidateFrozen(totalTokens, s.effectiveTokenThreshold(model)) {\n\t\t\t\t// Fresh tail grew too large — invalidate and re-stub\n\t\t\t\ts.logger.Printf(\"[req %d %s tid=%s] SAWTOOTH: frozen prefix expired (totalTokens=%dk > %dk threshold, combined=%dk: %dk frozen + %dk fresh + %dk overhead)\",\n\t\t\t\t\treqIdx, proj, threadID, totalTokens/1000, s.effectiveTokenThreshold(model)/1000, combinedTokens/1000, frozen.Tokens/1000, freshTokens/1000, overhead/1000)\n\t\t\t\ts.invalidateThreadCaches(threadID, proj, extractWorkingDirectory(req))\n\t\t\t\tfrozen = nil // fall through to trigger check below\n\t\t\t} else {\n\t\t\t\t// Use frozen prefix + fresh tail\n\t\t\t\tcombined := make([]any, 0, len(frozen.Messages)+len(freshMessages))\n\t\t\t\tcombined = append(combined, frozen.Messages...)\n\t\t\t\tcombined = append(combined, freshMessages...)\n\t\t\t\t// Eager-stub large tool_results in fresh tail (model already processed them)\n\t\t\t\tbeforeEager := s.countMessageTokens(combined[len(frozen.Messages):])\n\t\t\t\tvar stubSticky, stubFresh int\n\t\t\t\tcombined = EagerStubToolResults(combined, len(frozen.Messages), s.countTokens, WithStubMemory(s.eagerStubMemory, threadID), WithStubCounters(&stubSticky, &stubFresh))\n\t\t\t\tafterEager := s.countMessageTokens(combined[len(frozen.Messages):])\n\t\t\t\tif beforeEager != afterEager {\n\t\t\t\t\ts.logger.Printf(\"[req %d %s tid=%s] EAGER-STUB: fresh %dk → %dk (saved %dk) [sticky=%d fresh=%d]\", reqIdx, proj, threadID, beforeEager/1000, afterEager/1000, (beforeEager-afterEager)/1000, stubSticky, stubFresh)\n\t\t\t\t}\n\t\t\t\treq[\"messages\"] = combined\n\t\t\t\tneedsReserialization = true\n\t\t\t\t// Strip embedded CC breakpoints from frozen stubs — they waste cache slots.\n\t\t\t\t// InjectFrozenStubCacheBreakpoint adds exactly one below.\n\t\t\t\tif n := StripMessagesCacheControl(req, 0, len(frozen.Messages)); n > 0 {\n\t\t\t\t\ts.logger.Printf(\"[req %d %s tid=%s] SAWTOOTH: stripped %d embedded breakpoints from frozen stubs\", reqIdx, proj, threadID, n)\n\t\t\t\t}\n\t\t\t\ts.logger.Printf(\"%s[req %d %s tid=%s] SAWTOOTH: frozen prefix (%d stubs, %dk msg) + %d fresh (%dk msg) + %dk overhead = %dk total%s\",\n\t\t\t\t\tcolorGreen, reqIdx, proj, threadID, len(frozen.Messages), frozen.Tokens/1000, len(freshMessages), freshTokens/1000, overhead/1000, combinedTokens/1000, colorReset)\n\t\t\t\trawMsgTokens := s.countMessageTokens(messages)\n\t\t\t\ts.setRawEstimate(threadID, rawMsgTokens+overhead)\n\t\t\t\ts.logger.Printf(\"[req %d %s tid=%s] RAW ESTIMATE: msgTokens=%dk + overhead=%dk = %dk (messages=%d)\", reqIdx, proj, threadID, rawMsgTokens/1000, overhead/1000, (rawMsgTokens+overhead)/1000, len(messages))\n\t\t\t\tif InjectFrozenStubCacheBreakpoint(req, len(frozen.Messages)) {\n\t\t\t\t\ts.logger.Printf(\"[req %d %s tid=%s] SAWTOOTH: frozen stub cache breakpoint at messages[%d]\",\n\t\t\t\t\t\treqIdx, proj, threadID, len(frozen.Messages)-1)\n\t\t\t\t}\n\n\t\t\t\t// Ensure frozen stubs are persisted (may have failed on first request after deploy)\n\t\t\t\ts.ensureFrozenPersisted(threadID)\n\t\t\t}\n\t\t}\n\t\tif frozen == nil {\n\t\t\t// No frozen stubs — check trigger\n\t\t\ttriggerReason := s.sawtoothTrigger.ShouldTrigger(threadID, totalTokens)\n\t\t\tif triggerReason != TriggerNone {\n\t\t\t\ts.logger.Printf(\"[req %d %s tid=%s] SAWTOOTH stub-cycle triggered: %s (tokens=%dk)\",\n\t\t\t\t\treqIdx, proj, threadID, triggerReason, totalTokens/1000)\n\n\t\t\t\t// Refresh briefing during stub-cycle — disabled (briefing now via SessionStart hook)\n\t\t\t\t// s.refreshBriefing(proj)\n\t\t\t\t// if s.briefingText != \"\" {\n\t\t\t\t// \tUpsertSystemBlockCached(req, \"yesmem-briefing\", s.briefingText)\n\t\t\t\t// }\n\n\t\t\t\t// Run full stub pipeline\n\t\t\t\ts.setRawEstimate(threadID, totalTokens)\n\t\t\t\t_ = s.runStubCycle(messages, req, reqIdx, proj, threadID, overhead, totalTokens, userQuery, isRetryReq)\n\n\t\t\t\t// Freeze the result\n\t\t\t\tfinalMessages, _ := req[\"messages\"].([]any)\n\t\t\t\tif finalMessages != nil && len(finalMessages) > 0 {\n\t\t\t\t\t// Boundary = last message of original that maps to last stub\n\t\t\t\t\tcutoff := len(messages)\n\t\t\t\t\tsawtoothCutoff = cutoff\n\t\t\t\t\tvar boundaryMsg any\n\t\t\t\t\tif cutoff > 0 {\n\t\t\t\t\t\tboundaryMsg = messages[cutoff-1]\n\t\t\t\t\t}\n\t\t\t\t\tfrozenTokens := s.countMessageTokens(finalMessages)\n\t\t\t\t\tfrozenCount, stripped, breakpointInjected := s.freezeStubsAndInjectBreakpoint(req, threadID, cutoff, boundaryMsg, frozenTokens, totalTokens)\n\t\t\t\t\ts.decay.Persist(threadID)\n\t\t\t\t\tif stripped > 0 {\n\t\t\t\t\t\ts.logger.Printf(\"[req %d %s tid=%s] SAWTOOTH: stripped %d embedded breakpoints before freeze\",\n\t\t\t\t\t\t\treqIdx, proj, threadID, stripped)\n\t\t\t\t\t}\n\t\t\t\t\ts.logger.Printf(\"[req %d %s tid=%s] SAWTOOTH: frozen %d messages at cutoff=%d (~%dk tokens)\",\n\t\t\t\t\t\treqIdx, proj, threadID, frozenCount, cutoff, frozenTokens/1000)\n\t\t\t\t\tif breakpointInjected {\n\t\t\t\t\t\ts.logger.Printf(\"[req %d %s tid=%s] SAWTOOTH: frozen stub cache breakpoint at messages[%d]\",\n\t\t\t\t\t\t\treqIdx, proj, threadID, frozenCount-1)\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tneedsReserialization = true\n\t\t\t} else {\n\t\t\t\t// No trigger, no frozen stubs — eager-stub to delay first collapse\n\t\t\t\tbeforeEager := s.countMessageTokens(messages)\n\t\t\t\tvar stubSticky, stubFresh int\n\t\t\t\tmessages = EagerStubToolResults(messages, 0, s.countTokens, WithStubMemory(s.eagerStubMemory, threadID), WithStubCounters(&stubSticky, &stubFresh))\n\t\t\t\tafterEager := s.countMessageTokens(messages)\n\t\t\t\tif beforeEager != afterEager {\n\t\t\t\t\ts.logger.Printf(\"[req %d %s tid=%s] EAGER-STUB: %dk → %dk (saved %dk) [sticky=%d fresh=%d]\", reqIdx, proj, threadID, beforeEager/1000, afterEager/1000, (beforeEager-afterEager)/1000, stubSticky, stubFresh)\n\t\t\t\t\treq[\"messages\"] = messages\n\t\t\t\t\tneedsReserialization = true\n\t\t\t\t}\n\t\t\t\ts.logger.Printf(\"%s[req %d %s tid=%s] %d messages, ~%dk tokens (sawtooth: no trigger)%s\",\n\t\t\t\t\tcolorDim, reqIdx, proj, threadID, len(messages), totalTokens/1000, colorReset)\n\t\t\t}\n\t\t}\n\t} else {\n\t\t// Legacy path: progressive decay\n\t\tif s.shouldStub(totalTokens, model) {\n\t\t\ts.setRawEstimate(threadID, totalTokens)\n\t\t\t_ = s.runStubCycle(messages, req, reqIdx, proj, threadID, overhead, totalTokens, userQuery, isRetryReq)\n\t\t\tneedsReserialization = true\n\t\t}\n\t}\n\n\t// Persistent timestamp injection: re-injects stored timestamps on ALL previous user messages.\n\t// msg:N is computed from position (counting user messages in raw array), NOT from a counter.\n\t// The injected data is deterministic (same bytes every turn) = cache-safe after initial miss.\n\tif threadID != \"\" {\n\t\tif currentMsgs, ok := req[\"messages\"].([]any); ok && len(currentMsgs) > 0 {\n\t\t\ts.timestampStore.Load(threadID)\n\n\t\t\t// Offset = cutoff - stubsCount: maps combined array index to raw position.\n\t\t\t// Raw position of message at combined index I = cutoff + (I - stubs).\n\t\t\t// msgN = rawPos + 1 = cutoff + (I - stubs) + 1 = (cutoff - stubs) + I + 1.\n\t\t\tmsgOffset := sawtoothCutoff - sawtoothStubs\n\n\t\t\ts.logger.Printf(\"[req %d %s] timestamps: rawTotal=%d, postSawtooth=%d, cutoff=%d, stubs=%d, offset=%d\", reqIdx, proj, rawMsgCount, len(currentMsgs), sawtoothCutoff, sawtoothStubs, msgOffset)\n\n\t\t\t// Pre-store assistant timestamp BEFORE inject loop so it's available immediately\n\t\t\ts.responseTsMu.RLock()\n\t\t\trespTime, hasRespTime := s.responseTimes[threadID]\n\t\t\ts.responseTsMu.RUnlock()\n\t\t\tif hasRespTime {\n\t\t\t\trespStr := shortWeekday(respTime.Weekday()) + \" \" + respTime.Format(\"2006-01-02 15:04:05\")\n\t\t\t\tfor j := len(currentMsgs) - 2; j >= 0; j-- {\n\t\t\t\t\tprevMsg, _ := currentMsgs[j].(map[string]any)\n\t\t\t\t\tif prevMsg == nil || prevMsg[\"role\"] != \"assistant\" {\n\t\t\t\t\t\tcontinue\n\t\t\t\t\t}\n\t\t\t\t\tif !msgHasTextBlock(prevMsg) {\n\t\t\t\t\t\ts.logger.Printf(\"[req %d %s] timestamps: skip assistant[%d] no text block\", reqIdx, proj, j)\n\t\t\t\t\t\tcontinue // skip tool-use-only assistant messages\n\t\t\t\t\t}\n\t\t\t\t\tprevMsgN := msgOffset + j + 1\n\t\t\t\t\tif _, exists := s.timestampStore.Get(threadID, prevMsgN); !exists {\n\t\t\t\t\t\ts.timestampStore.Store(threadID, prevMsgN, &TimestampMeta{Timestamp: respStr})\n\t\t\t\t\t\ts.logger.Printf(\"[req %d %s] timestamps: stored assistant msg:%d ts=%s\", reqIdx, proj, prevMsgN, respStr)\n\t\t\t\t\t} else {\n\t\t\t\t\t\ts.logger.Printf(\"[req %d %s] timestamps: assistant msg:%d already has ts\", reqIdx, proj, prevMsgN)\n\t\t\t\t\t}\n\t\t\t\t\tbreak\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\ts.logger.Printf(\"[req %d %s] timestamps: no responseTimes for thread\", reqIdx, proj)\n\t\t\t}\n\n\t\t\t// Inject [msg:N] (+ timestamp/delta if stored) on all PREVIOUS messages\n\t\t\tif isFeatureEnabled(&s.cfg, model, \"timestamps\") {\n\t\t\t\tif n := InjectTimestamps(s.timestampStore, threadID, currentMsgs, len(currentMsgs)-1, msgOffset, sawtoothStubs); n > 0 {\n\t\t\t\t\tneedsReserialization = true\n\t\t\t\t\ts.logger.Printf(\"[req %d %s] timestamps: injected %d\", reqIdx, proj, n)\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t// Current (last) user message: full timestamp + delta + msg:N from raw count\n\t\t\tlastMsg, _ := currentMsgs[len(currentMsgs)-1].(map[string]any)\n\t\t\tif lastMsg != nil && lastMsg[\"role\"] == \"user\" && !msgHasMetaPrefix(lastMsg) {\n\t\t\t\tnow := time.Now()\n\t\t\t\tnowStr := shortWeekday(now.Weekday()) + \" \" + now.Format(\"2006-01-02 15:04:05\")\n\t\t\t\tentry := &TimestampMeta{Timestamp: nowStr}\n\t\t\t\tif hasRespTime {\n\t\t\t\t\tentry.Delta = formatDelta(now.Sub(respTime))\n\t\t\t\t}\n\t\t\t\ts.timestampStore.Store(threadID, rawMsgCount, entry)\n\t\t\t\tif isFeatureEnabled(&s.cfg, model, \"timestamps\") {\n\t\t\t\t\tprependMeta(lastMsg, BuildMeta(rawMsgCount, entry))\n\t\t\t\t}\n\t\t\t\tneedsReserialization = true\n\t\t\t}\n\n\t\t\ts.timestampStore.Persist(threadID)\n\t\t}\n\t}\n\n\t// Associative context injection (for ALL requests, not just stubbed ones)\n\t// Re-enabled with higher thresholds (55/75, max 1) after disabling Fresh Remember.\n\tassocContext := s.findAssociativeContextFor(userQuery, proj, threadID, messages)\n\tif assocContext != \"\" {\n\t\tcurrentMessages, _ := req[\"messages\"].([]any)\n\t\tif currentMessages == nil {\n\t\t\tcurrentMessages = messages\n\t\t}\n\t\treq[\"messages\"] = injectAssociativeContext(currentMessages, assocContext, s.cfg.SawtoothEnabled)\n\t\tneedsReserialization = true\n\t\ts.logger.Printf(\"%s[req %d %s tid=%s] associative context injected%s\", colorBlue, reqIdx, proj, threadID, colorReset)\n\t}\n\n\t// Doc-chunk context injection: separate search path for indexed docs (not skills)\n\tdocContext := s.findDocContextFor(userQuery, proj, messages)\n\tif docContext != \"\" {\n\t\tcurrentMessages, _ := req[\"messages\"].([]any)\n\t\tif currentMessages == nil {\n\t\t\tcurrentMessages = messages\n\t\t}\n\t\treq[\"messages\"] = injectAssociativeContext(currentMessages, docContext, s.cfg.SawtoothEnabled)\n\t\tneedsReserialization = true\n\t\ts.logger.Printf(\"%s[req %d %s tid=%s] doc context injected%s\", colorBlue, reqIdx, proj, threadID, colorReset)\n\t}\n\n\t// Fresh Remember Injection: DISABLED\n\t// Causes echo-loop: Claude saves via remember(), sees it again next turn as\n\t// [yesmem fresh memory], reacts to own output instead of user's request.\n\t// Recovery after /clear is handled by recovery.go (briefing), not here.\n\t// Subagents get their own briefing. No remaining use-case.\n\t// remItems := s.popRecentRemember()\n\tvar remItems []recentLearningItem\n\n\t// Rules re-injection: inject condensed CLAUDE.md rules every ~40k output tokens\n\tif rulesBlock := s.rulesInject(threadID, totalTokens, proj); rulesBlock != \"\" {\n\t\tcurrentMessages, _ := req[\"messages\"].([]any)\n\t\tif currentMessages == nil {\n\t\t\tcurrentMessages = messages\n\t\t}\n\t\treq[\"messages\"] = injectAssociativeContext(currentMessages, s.formatRulesReminder(rulesBlock, proj, false), s.cfg.SawtoothEnabled)\n\t\tneedsReserialization = true\n\t\ts.logger.Printf(\"%s[req %d %s tid=%s] rules reminder injected%s\", colorBlue, reqIdx, proj, threadID, colorReset)\n\t}\n\n\t// Plan auto-activation: nudge Claude to call set_plan() when reading a plan file\n\tif planFile := detectPlanFileRead(messages); planFile != \"\" {\n\t\tif activePlan, _ := s.getActivePlan(threadID); shouldNudgePlan(planFile, activePlan) {\n\t\t\tAppendSystemBlock(req, \"plan-nudge\",\n\t\t\t\tfmt.Sprintf(\"Du hast einen Implementierungsplan gelesen (%s). Aktiviere ihn jetzt via set_plan() damit Plan-Checkpoints funktionieren und dein Fortschritt getrackt wird.\", filepath.Base(planFile)))\n\t\t\tneedsReserialization = true\n\t\t\ts.logger.Printf(\"%s[req %d %s tid=%s] plan nudge injected for %s%s\", colorBlue, reqIdx, proj, threadID, filepath.Base(planFile), colorReset)\n\t\t}\n\t}\n\n\t// Plan checkpoint: inject plan reminder every ~20k tokens if active plan exists\n\ts.detectPlanToolCall(messages, threadID, totalTokens)\n\tif checkpoint := s.planCheckpointInject(threadID, totalTokens); checkpoint != \"\" {\n\t\tcurrentMessages, _ := req[\"messages\"].([]any)\n\t\tif currentMessages == nil {\n\t\t\tcurrentMessages = messages\n\t\t}\n\t\treq[\"messages\"] = injectAssociativeContext(currentMessages, checkpoint, s.cfg.SawtoothEnabled)\n\t\tneedsReserialization = true\n\t\ts.logger.Printf(\"%s[req %d %s tid=%s] plan checkpoint injected%s\", colorBlue, reqIdx, proj, threadID, colorReset)\n\t}\n\n\t// Docs hint injection: inject docs-available reminder every ~10k tokens (independent of plan)\n\tif docsHint := s.docsHintInject(threadID, totalTokens, proj); docsHint != \"\" {\n\t\tcurrentMessages, _ := req[\"messages\"].([]any)\n\t\tif currentMessages == nil {\n\t\t\tcurrentMessages = messages\n\t\t}\n\t\treq[\"messages\"] = injectAssociativeContext(currentMessages, docsHint, s.cfg.SawtoothEnabled)\n\t\tneedsReserialization = true\n\t\ts.logger.Printf(\"%s[req %d %s tid=%s] docs hint injected%s\", colorBlue, reqIdx, proj, threadID, colorReset)\n\t}\n\tif len(remItems) > 0 {\n\t\tvar lines []string\n\t\tfor _, item := range remItems {\n\t\t\tif item.ID > 0 {\n\t\t\t\tlines = append(lines, fmt.Sprintf(\"- [ID:%d] %s\", item.ID, truncateStr(item.Text, 200)))\n\t\t\t} else {\n\t\t\t\tlines = append(lines, \"- \"+truncateStr(item.Text, 200))\n\t\t\t}\n\t\t}\n\t\tremContext := fmt.Sprintf(\"[yesmem fresh memory]\\nGerade gelernt:\\n%s\\n%s\\n[/yesmem fresh memory]\", strings.Join(lines, \"\\n\"), InlineReflectionHint)\n\t\tcurrentMessages, _ := req[\"messages\"].([]any)\n\t\tif currentMessages == nil {\n\t\t\tcurrentMessages = messages\n\t\t}\n\t\treq[\"messages\"] = injectAssociativeContext(currentMessages, remContext, s.cfg.SawtoothEnabled)\n\t\tneedsReserialization = true\n\t\ts.logger.Printf(\"%s[req %d %s tid=%s] fresh remember injected (%d items)%s\", colorBlue, reqIdx, proj, threadID, len(remItems), colorReset)\n\t}\n\n\t// Skill eval injection: mandatory evaluation instruction for real user input only\n\tif threadID != \"\" && proj != \"\" && isUserInputTurn(messages) {\n\t\t// First: detect any skill activations from message history (updates tracker)\n\t\ts.syncSkillActivations(messages, proj, threadID)\n\n\t\t// Inject mandatory skill evaluation block into last user message\n\t\tskillEval := buildSkillEvalBlock(s.cfg.SkillEvalInject)\n\t\tcurrentMessages, _ := req[\"messages\"].([]any)\n\t\tif currentMessages == nil {\n\t\t\tcurrentMessages = messages\n\t\t}\n\t\tif skillEval != \"\" {\n\t\t\treq[\"messages\"] = injectAssociativeContext(currentMessages, skillEval, s.cfg.SawtoothEnabled)\n\t\t\tneedsReserialization = true\n\t\t\ts.logger.Printf(\"%s[req %d %s tid=%s] skill-eval injected (mode=%s)%s\", colorBlue, reqIdx, proj, threadID, s.cfg.SkillEvalInject, colorReset)\n\t\t}\n\t}\n\n\t// === Collect injected learning IDs for next turn's inline reflection ===\n\tif threadID != \"\" {\n\t\tcurrentIDs := make(map[int64]string) // id → source\n\t\t// Source 1: Briefing learnings (cached, same every turn)\n\t\tbriefingSnapshot, _, _ := s.getCachedBriefing(threadID, proj)\n\t\tif briefingSnapshot != \"\" {\n\t\t\tfor _, m := range idPattern.FindAllStringSubmatch(briefingSnapshot, -1) {\n\t\t\t\tif id, err := strconv.ParseInt(m[1], 10, 64); err == nil {\n\t\t\t\t\tcurrentIDs[id] = \"briefing\"\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\t// Source 2: Associative context (contextual, per-turn)\n\t\tif assocContext != \"\" {\n\t\t\tfor _, m := range idPattern.FindAllStringSubmatch(assocContext, -1) {\n\t\t\t\tif id, err := strconv.ParseInt(m[1], 10, 64); err == nil {\n\t\t\t\t\tcurrentIDs[id] = \"associative\" // overwrites briefing if both — associative is the stronger signal\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\t// Source 3: Fresh remember items (just learned)\n\t\tfor _, item := range remItems {\n\t\t\tif item.ID > 0 {\n\t\t\t\tcurrentIDs[item.ID] = \"fresh\"\n\t\t\t}\n\t\t}\n\t\ts.lastInjectedIDsMu.Lock()\n\t\tif len(currentIDs) > 0 {\n\t\t\t// Accumulate IDs across turns — don't overwrite.\n\t\t\t// Claude may reference an ID from turn 30 in turn 50.\n\t\t\texisting := s.lastInjectedIDs[threadID]\n\t\t\tif existing == nil {\n\t\t\t\texisting = make(map[int64]string)\n\t\t\t}\n\t\t\tfor id, source := range currentIDs {\n\t\t\t\texisting[id] = source\n\t\t\t}\n\t\t\ts.lastInjectedIDs[threadID] = existing\n\t\t}\n\t\ts.lastInjectedIDsMu.Unlock()\n\t}\n\n\t// Think reminder injection — only for real user turns, not tool continuations\n\tif threadID != \"\" {\n\t\tcurrentMessages, _ := req[\"messages\"].([]any)\n\t\tif currentMessages == nil {\n\t\t\tcurrentMessages = messages\n\t\t}\n\t\tif lastUserHasText(currentMessages) {\n\t\t\tthinkReminder := s.buildThinkReminder(threadID, threadID, false)\n\t\t\tif thinkReminder != \"\" {\n\t\t\t\treq[\"messages\"] = injectAssociativeContext(currentMessages, thinkReminder, s.cfg.SawtoothEnabled)\n\t\t\t\tneedsReserialization = true\n\t\t\t}\n\t\t}\n\n\t\t// Refresh frozen-prefix snapshot with the post-timestamps bytes that\n\t\t// will appear in the wire body for the frozen range. This MUST run\n\t\t// BEFORE injectBriefingTurn / injectCodeMapTurn / injectCapabilitiesTurn:\n\t\t// those stages prepend or insert turns into req[\"messages\"], which\n\t\t// shifts the frozen range to a higher offset. Slicing\n\t\t// req[\"messages\"][:frozenLen] AFTER inject would capture the injected\n\t\t// turns at the head and corrupt the stored frozen prefix, breaking\n\t\t// cache continuity for 5-9 turns after every emergency-sawtooth.\n\t\tif frozenLen := s.frozenStubs.LengthFor(threadID); frozenLen > 0 {\n\t\t\tif msgs, ok := req[\"messages\"].([]any); ok && len(msgs) >= frozenLen {\n\t\t\t\tif s.frozenStubs.UpdateMessages(threadID, msgs[:frozenLen]) {\n\t\t\t\t\ts.logger.Printf(\"[req %d %s tid=%s] FROZEN-SNAPSHOT: refreshed %d pre-inject messages\", reqIdx, proj, threadID, frozenLen)\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\t// Briefing injection: prepend user/assistant turn pair at beginning of messages.\n\t\t// Static per session → stable prefix → cacheable. Must be before dialog injection.\n\t\tif s.injectBriefingTurn(req, reqIdx, proj, threadID) {\n\t\t\tneedsReserialization = true\n\t\t}\n\n\t\t// Code Map injection: insert after briefing turn pair.\n\t\tif s.injectCodeMapTurn(req, reqIdx, proj, threadID) {\n\t\t\tneedsReserialization = true\n\t\t}\n\n\t\t// Active capabilities injection: insert user/assistant pair directly\n\t\t// before the last user message. Fresh per turn (no cache) so\n\t\t// activate/deactivate takes effect immediately. Must run AFTER\n\t\t// briefing/codeMap (both prepend at beginning) and BEFORE dialog\n\t\t// injection (which appends at end) to preserve cache for the\n\t\t// briefing prefix and keep the injection close to the real user\n\t\t// turn.\n\t\tif s.injectCapabilitiesTurn(req, threadID) {\n\t\t\tneedsReserialization = true\n\t\t}\n\n\t\t// Dialog injection: append at end of messages with pseudo-padding for alternation.\n\t\t// Always works regardless of tool_result chains or conversation state.\n\t\tdr := s.checkDialogMessages(threadID, proj)\n\t\tif dr.Extra != \"\" {\n\t\t\t// Prepend session ID for agent-to-agent communication\n\t\t\tdr.Extra = fmt.Sprintf(\"DEINE_SESSION_ID: %s\\n\", threadID) + dr.Extra\n\t\t\tinjected := false\n\t\t\tif msgs, ok := req[\"messages\"].([]any); ok && len(msgs) > 0 {\n\t\t\t\t// Pad with empty assistant if last message is user (maintains alternation)\n\t\t\t\tlastMsg, _ := msgs[len(msgs)-1].(map[string]any)\n\t\t\t\tif lastMsg != nil && lastMsg[\"role\"] == \"user\" {\n\t\t\t\t\tmsgs = append(msgs, map[string]any{\n\t\t\t\t\t\t\"role\": \"assistant\",\n\t\t\t\t\t\t\"content\": \"​\", // zero-width space — invisible padding\n\t\t\t\t\t})\n\t\t\t\t}\n\t\t\t\t// Append dialog as new user message (becomes the last message Claude sees)\n\t\t\t\tmsgs = append(msgs, map[string]any{\n\t\t\t\t\t\"role\": \"user\",\n\t\t\t\t\t\"content\": \"\\n\" + dr.Extra + \"\\n\",\n\t\t\t\t})\n\t\t\t\treq[\"messages\"] = msgs\n\t\t\t\tneedsReserialization = true\n\t\t\t\tinjected = true\n\t\t\t}\n\t\t\t// Mark as read after 3 injection turns — long enough for Claude to process,\n\t\t\t// resilient against fast ping-pong sessions (time-based was broken for <30s turns).\n\t\t\tif injected && dr.HasUnread && dr.SessionID != \"\" {\n\t\t\t\tif s.shouldMarkChannelRead(dr) {\n\t\t\t\t\ts.markDialogRead(dr)\n\t\t\t\t}\n\t\t\t}\n\t\t\ts.logger.Printf(\"[dialog] injection injected=%v extra_len=%d\", injected, len(dr.Extra))\n\t\t}\n\t}\n\n\t// Signal tools are NOT injected into the main request.\n\t// They are collected via a separate async reflection call after the response.\n\t// See fireReflectionCall() in reflection.go.\n\t// NEVER put _signal_* tools back here — the model ignores them among 44+ real tools.\n\n\t// Legacy assistant annotation: timestamp on last assistant message after cache breakpoint.\n\t// User timestamps are now handled by TimestampStore (injected earlier in the pipeline).\n\tif threadID != \"\" {\n\t\tif msgs, ok := req[\"messages\"].([]any); ok && len(msgs) > 0 {\n\t\t\t// Find last cache_control breakpoint in messages\n\t\t\tcacheIdx := -1\n\t\t\tfor i, m := range msgs {\n\t\t\t\tmsg, _ := m.(map[string]any)\n\t\t\t\tif msg == nil {\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\t\t\t\tif _, has := msg[\"cache_control\"]; has {\n\t\t\t\t\tcacheIdx = i\n\t\t\t\t}\n\t\t\t\tif content, ok := msg[\"content\"].([]any); ok {\n\t\t\t\t\tfor _, block := range content {\n\t\t\t\t\t\tif b, ok := block.(map[string]any); ok {\n\t\t\t\t\t\t\tif _, has := b[\"cache_control\"]; has {\n\t\t\t\t\t\t\t\tcacheIdx = i\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t\tstartIdx := cacheIdx + 1\n\t\t\tif startIdx < 0 {\n\t\t\t\tstartIdx = 0\n\t\t\t}\n\t\t\ts.responseTsMu.RLock()\n\t\t\trespTime, hasRespTime := s.responseTimes[threadID]\n\t\t\ts.responseTsMu.RUnlock()\n\t\t\tif hasRespTime {\n\t\t\t\tfor i := len(msgs) - 1; i >= startIdx; i-- {\n\t\t\t\t\tmsg, _ := msgs[i].(map[string]any)\n\t\t\t\t\tif msg != nil && msg[\"role\"] == \"assistant\" {\n\t\t\t\t\t\tmeta := fmt.Sprintf(\"[%s] [msg:%d]\", respTime.Format(\"2006-01-02 15:04:05\"), i)\n\t\t\t\t\t\tprependMeta(msg, meta)\n\t\t\t\t\t\tneedsReserialization = true\n\t\t\t\t\t\tbreak\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\t// Self-Priming: DISABLED (experimental, see signal_handlers.go comment block).\n\t// The reflection-based selfprime (Haiku) was too inaccurate to be useful —\n\t// the main model adapts to user context organically from message history.\n\t// Kept for reference; re-enable by uncommenting below + setting ShouldActivate=true.\n\t//\n\t// s.selfPrimeMu.RLock()\n\t// anchor := s.selfPrimes[threadID]\n\t// s.selfPrimeMu.RUnlock()\n\t// if anchor != \"\" {\n\t// \tif msgs, ok := req[\"messages\"].([]any); ok && len(msgs) > 0 {\n\t// \t\tlastMsg, _ := msgs[len(msgs)-1].(map[string]any)\n\t// \t\tif lastMsg != nil && lastMsg[\"role\"] == \"user\" {\n\t// \t\t\tprimeBlock := map[string]any{\n\t// \t\t\t\t\"type\": \"text\",\n\t// \t\t\t\t\"text\": \"[self-prime]\\n[Self-Prime von deiner letzten Antwort]: \" + anchor,\n\t// \t\t\t}\n\t// \t\t\tif content, ok := lastMsg[\"content\"].([]any); ok {\n\t// \t\t\t\tlastMsg[\"content\"] = append(content, primeBlock)\n\t// \t\t\t} else if text, ok := lastMsg[\"content\"].(string); ok {\n\t// \t\t\t\tlastMsg[\"content\"] = []any{\n\t// \t\t\t\t\tmap[string]any{\"type\": \"text\", \"text\": text},\n\t// \t\t\t\t\tprimeBlock,\n\t// \t\t\t\t}\n\t// \t\t\t}\n\t// \t\t\tneedsReserialization = true\n\t// \t\t}\n\t// \t}\n\t// }\n\n\t// Prompt Caching: inject cache_control breakpoints on system + tools\n\t// Sawtooth: disabled (all 4 slots are used by CC system (2) + briefing (1) + CC messages (1))\n\t// Legacy: count existing breakpoints and fill up remaining slots.\n\tif !s.cfg.SawtoothEnabled && s.cacheGate.ShouldCache() {\n\t\tif n := InjectCacheBreakpoints(req, s.logger); n > 0 {\n\t\t\tneedsReserialization = true\n\t\t\ts.logger.Printf(\"[req %d %s] prompt cache: %d breakpoints injected\", reqIdx, proj, n)\n\t\t} else {\n\t\t\ts.logger.Printf(\"[req %d %s] prompt cache: no budget (4/4 already set)\", reqIdx, proj)\n\t\t}\n\t}\n\n\t// Final: upgrade cache TTL + enforce breakpoint limit (must run AFTER all injections + InjectCacheBreakpoints)\n\t// Shift the last messages-breakpoint from unstable user message to stable assistant message.\n\t// Text user messages change between turns (timestamps, reminders, structure); assistant messages don't.\n\t// Tool-result messages are excluded — they are stable cache anchors.\n\tif ShiftMessageBreakpoint(req) {\n\t\tneedsReserialization = true\n\t}\n\t// Normalize cache_control TTL across all blocks to prevent Anthropic from\n\t// rejecting requests with increasing TTLs in block order. Auto-detects 1h\n\t// when any existing block already carries it (config, Claude Code\n\t// passthrough on --resume, or stale frozen prefix from an earlier turn).\n\tif n := NormalizeCacheTTL(req, s.cfg.CacheTTL); n > 0 {\n\t\tneedsReserialization = true\n\t\ts.logger.Printf(\"[req %d %s] cache TTL normalized: %d blocks\", reqIdx, proj, n)\n\t}\n\tif n := EnforceCacheBreakpointLimit(req, maxCacheBreakpoints); n > 0 {\n\t\tneedsReserialization = true\n\t\ts.logger.Printf(\"[req %d %s] prompt cache: trimmed %d surplus breakpoints\", reqIdx, proj, n)\n\t}\n\n\t// Pre-flight: validate tool_use/tool_result pairing before forwarding.\n\t// Repairs orphans created by Stubify/Collapse/Injection pipeline.\n\tif msgs, ok := req[\"messages\"].([]any); ok {\n\t\trepaired, orphans := validateToolPairs(msgs, s.logger)\n\t\tif orphans > 0 {\n\t\t\treq[\"messages\"] = repaired\n\t\t\ts.logger.Printf(\"%s[req %d %s tid=%s] validate: repaired %d orphan tool_result(s)%s\", colorOrange, reqIdx, proj, threadID, orphans, colorReset)\n\t\t\tneedsReserialization = true\n\t\t}\n\t}\n\n\t// Early-out: skip re-serialization when nothing changed\n\tif !needsReserialization {\n\t\ts.logger.Printf(\"%s[req %d %s tid=%s] %d messages, ~%dk tokens (no stubbing, threshold=%dk)%s\", colorDim, reqIdx, proj, threadID, len(messages), totalTokens/1000, s.effectiveTokenThreshold(model)/1000, colorReset)\n\t\ts.forwardWithAnnotation(w, r, body, reqIdx, toolUseIDs, proj, threadID, msgCount, totalTokens)\n\t\treturn\n\t}\n\n\t// Re-serialize\n\tbody, err = json.Marshal(req)\n\tif err != nil {\n\t\ts.logger.Printf(\"%s[req %d %s tid=%s] re-marshal error: %v%s\", colorRed, reqIdx, proj, threadID, err, colorReset)\n\t\thttp.Error(w, \"internal proxy error\", http.StatusInternalServerError)\n\t\treturn\n\t}\n\n\t// Debug: dump final request body to file (enable with YESMEM_PROXY_DEBUG=1)\n\tif needsReserialization && os.Getenv(\"YESMEM_PROXY_DEBUG\") == \"1\" {\n\t\tdumpDir := filepath.Join(s.cfg.DataDir, \"debug\")\n\t\tos.MkdirAll(dumpDir, 0755)\n\t\tts := time.Now().Format(\"20060102-150405\")\n\t\tdumpPath := fmt.Sprintf(\"%s/req_%s_%d.json\", dumpDir, ts, reqIdx)\n\t\tos.WriteFile(dumpPath, body, 0644)\n\t\ts.logger.Printf(\"[req %d] dumped %dk to %s\", reqIdx, len(body)/1024, dumpPath)\n\t}\n\n\t// Task #7: Mark this request fingerprint for retry detection\n\tif !isRetryReq {\n\t\ts.markRequest(fp)\n\t}\n\n\t// Forward to Anthropic with SSE annotation extraction\n\t// Use original msgCount (pre-injection) so actual-based estimation stays consistent across turns\n\t// Use actual-based totalTokens as estimate when available (more accurate than full recount)\n\tfinalEstimate := totalTokens\n\tif s.sawtoothTrigger == nil || s.sawtoothTrigger.GetLastTokens(threadID) == 0 {\n\t\t// First request: no actual yet, do a full recount of post-injection messages\n\t\tfinalMsgs, _ := req[\"messages\"].([]any)\n\t\tfinalEstimate = s.measureOverhead(req) + s.countMessageTokens(finalMsgs)\n\t}\n\ts.logger.Printf(\"[req %d %s tid=%s] FINAL: ~%dk estimated (after all injections)\", reqIdx, proj, threadID, finalEstimate/1000)\n\tif raw := s.getRawEstimate(threadID); raw > 0 {\n\t\ts.cacheStatusWriter.UpdateRawForThread(threadID, raw)\n\t}\n\ts.forwardWithAnnotation(w, r, body, reqIdx, toolUseIDs, proj, threadID, msgCount, finalEstimate)\n}\n\n// getPromptFlags resolves the effective prompt flags for a profile, falling back\n// to the Server's legacy flat config if promptCfg is nil (backward compatibility).\nfunc (s *Server) getPromptFlags(profile models.PromptProfile) *config.PromptFlags {\n\tif s.promptCfg != nil {\n\t\treturn s.promptCfg.EffectivePromptFlags(profile)\n\t}\n\t// Legacy path: use flat fields as Claude defaults.\n\treturn &config.PromptFlags{\n\t\tUngate: s.cfg.PromptUngate,\n\t\tRewrite: s.cfg.PromptRewrite,\n\t\tEnhance: s.cfg.PromptEnhance,\n\t\tToolPrefs: s.cfg.PromptToolPrefs,\n\t\tOutputDiscipline: s.cfg.PromptOutputDiscipline,\n\t\tCodingDiscipline: s.cfg.PromptCodingDiscipline,\n\t\tBeweislast: s.cfg.PromptBeweislast,\n\t\tScopeDiscipline: s.cfg.PromptScopeDiscipline,\n\t\tDelegationContract: s.cfg.PromptDelegationContract,\n\t\tClarifyFirst: s.cfg.PromptClarifyFirst,\n\t\tCodeToolsFirst: s.cfg.PromptCodeToolsFirst,\n\t\tWikiFirst: s.cfg.PromptWikiFirst,\n\t\tPatternSuggest: s.cfg.PromptPatternSuggest,\n\t}\n}\n\n// getThreadCWD returns the cached working directory for a thread.\nfunc (s *Server) getThreadCWD(threadID string) string {\n\ts.threadCWDMu.RLock()\n\tdefer s.threadCWDMu.RUnlock()\n\tif s.threadCWD == nil {\n\t\treturn \"\"\n\t}\n\treturn s.threadCWD[threadID]\n}\n\n// setThreadCWD caches the working directory for a thread.\nfunc (s *Server) setThreadCWD(threadID, cwd string) {\n\ts.threadCWDMu.Lock()\n\tdefer s.threadCWDMu.Unlock()\n\tif s.threadCWD == nil {\n\t\ts.threadCWD = make(map[string]string)\n\t}\n\ts.threadCWD[threadID] = cwd\n}\n\n// resolveOpenAITarget returns the upstream URL for an OpenAI-format request.\n// Checks ProviderTargets by prefix match (e.g. \"deepseek\" matches \"deepseek-v4-pro\"),\n// then by exact model name, then falls back to OpenAITargetURL.\nfunc (s *Server) resolveOpenAITarget(model string) string {\n\tmodelLower := strings.ToLower(model)\n\tif len(s.cfg.ProviderTargets) > 0 {\n\t\tfor key, url := range s.cfg.ProviderTargets {\n\t\t\tif url == \"\" {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tkeyLower := strings.ToLower(key)\n\t\t\tif strings.HasPrefix(modelLower, keyLower) || strings.HasPrefix(modelLower, keyLower+\"-\") {\n\t\t\t\treturn strings.TrimRight(url, \"/\")\n\t\t\t}\n\t\t}\n\t\tfor key, url := range s.cfg.ProviderTargets {\n\t\t\tif url != \"\" && strings.EqualFold(key, model) {\n\t\t\t\treturn strings.TrimRight(url, \"/\")\n\t\t\t}\n\t\t}\n\t}\n\t// Auto-discovered targets (secondary — explicit provider_targets takes priority)\n\tif len(s.autoProviderTargets) > 0 {\n\t\tif url, ok := s.autoProviderTargets[modelLower]; ok && url != \"\" {\n\t\t\treturn url\n\t\t}\n\t}\n\n\tif s.cfg.OpenAITargetURL != \"\" {\n\t\treturn s.cfg.OpenAITargetURL\n\t}\n\treturn s.cfg.TargetURL\n}\n\n// resolveAnthropicTarget returns the upstream URL for an Anthropic-format request.\n// Checks ProviderTargets by prefix match (e.g. \"deepseek\" matches \"deepseek-v4-pro\"),\n// then by exact model name, then falls back to TargetURL.\nfunc (s *Server) resolveAnthropicTarget(model string) string {\n\tmodelLower := strings.ToLower(model)\n\tif len(s.cfg.ProviderTargets) > 0 {\n\t\tfor key, url := range s.cfg.ProviderTargets {\n\t\t\tif url == \"\" {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tkeyLower := strings.ToLower(key)\n\t\t\tif strings.HasPrefix(modelLower, keyLower) || strings.HasPrefix(modelLower, keyLower+\"-\") {\n\t\t\t\treturn strings.TrimRight(url, \"/\")\n\t\t\t}\n\t\t}\n\t\tfor key, url := range s.cfg.ProviderTargets {\n\t\t\tif url != \"\" && strings.EqualFold(key, model) {\n\t\t\t\treturn strings.TrimRight(url, \"/\")\n\t\t\t}\n\t\t}\n\t}\n\treturn s.cfg.TargetURL\n}\n\n// bytesPerToken is the approximation factor for token estimation.\n// Anthropic recommends ~3.5 chars/token; we use 3.6 for slight overestimation\n// (triggers stubbing a bit earlier = safer for context budget).\nconst bytesPerToken = 3.6\n\n// TokenEstimateFunc is the central token estimation function.\n// All stubbing/budget decisions use this. Receives extracted text content\n// (not JSON wire format) so the tokenizer counts actual content tokens.\ntype TokenEstimateFunc func(text string) int\n"}, "6b5ae8559be53acfb5a945cba82035f964952a57": {"t": 1785759349.610403, "n": 67350, "h": "e30adc9af6e4bbc3", "c": "package proxy\n\nimport (\n\t\"context\"\n\t\"crypto/tls\"\n\t\"encoding/json\"\n\t\"fmt\"\n\t\"io\"\n\t\"log\"\n\t\"net\"\n\t\"net/http\"\n\t\"os\"\n\t\"os/signal\"\n\t\"path/filepath\"\n\t\"strconv\"\n\t\"strings\"\n\t\"sync\"\n\t\"sync/atomic\"\n\t\"syscall\"\n\t\"time\"\n\n\ttokenizer \"github.com/qhenkart/anthropic-tokenizer-go\"\n)\n\n// Config holds proxy configuration.\ntype Config struct {\n\tListenAddr string // e.g. \":9099\"\n\tTargetURL string // e.g. \"https://api.anthropic.com\"\n\tTokenThreshold int // trigger stubbing above this estimated token count\n\tTokenMinimumThreshold int // stub down to this floor\n\tTokenThresholds map[string]int // model-specific thresholds: {\"opus\": 180000, \"haiku\": 130000}\n\tKeepRecent int // number of messages to always keep unmodified\n\tDataDir string // yesmem data directory for DB access\n\tOpenAITargetURL string // upstream for OpenAI-format clients; if empty, uses TargetURL\n\n\t// Signal reflection\n\tSignalsEnabled bool // enable async signal reflection calls\n\tSignalsMode string // \"reflection\" = separate API call\n\tSignalsEveryNTurns int // every N end_turn responses\n\tSignalsModel string // full model ID, e.g. \"claude-sonnet-4-6\"\n\tAPIKey string // needed for reflection API calls\n\n\t// Sawtooth cache optimization\n\tSawtoothEnabled bool // use sawtooth instead of progressive decay (default: true)\n\tCacheTTL string // \"ephemeral\" (5m) or \"1h\" (extended)\n\n\t// Usage deflation: scale down input_tokens reported to CC to suppress \"Context low\" warning.\n\t// 0 = disabled, 0.7 = report 70% of actual tokens. Real values kept for internal tracking.\n\tUsageDeflationFactor float64\n\n\t// System prompt rewriting\n\tPromptUngate bool // strip \"may or may not be relevant\" disclaimer from CLAUDE.md injection\n\tPromptRewrite bool // strip \"Output efficiency\" + \"short and concise\", inject Ant-quality directives\n\tPromptEnhance bool // CLAUDE.md authority reinforcement, comment discipline, persona-based tone\n\tPromptToolPrefs bool // inject [yesmem-tool-prefs] Edit/Write preference + error-semantics warning\n\tPromptOutputDiscipline bool // inject [yesmem-output-discipline] no-preamble + no-skill-eval + exploratory-heuristic\n\tPromptCodingDiscipline bool // inject [yesmem-coding-discipline] read-before-propose + no-brute-force + no-half-finished\n\tPromptBeweislast bool // inject [yesmem-beweislast] fabrication-guard + claim-vs-proof + stance-under-challenge + tool-result-honesty + long-context-erosion\n\tPromptScopeDiscipline bool // inject [yesmem-scope-discipline] deliver-A-not-A+B+C + adjacent-findings-separate + scope-bound-authorization\n\tPromptDelegationContract bool // inject [yesmem-delegation-contract] self-contained-prompts + parallel-dispatch\n\tPromptClarifyFirst bool // inject [yesmem-clarify-first] clarify only when alternative interpretations produce materially different work\n\tPromptCodeToolsFirst bool // inject [yesmem-code-tools-first] prefer MCP code-navigation tools over Agent spawns\n\tPromptPatternSuggest bool // record repeated shell-command shapes for offline cap-suggestion analysis\n\tEffortFloor string // minimum effort level: \"low\", \"medium\", \"high\", \"max\" (empty = off)\n\tSkillEvalInject string // \"true\" = verbose eval, \"silent\" = internal eval only, \"false\" = disabled\n\n\t// Cache keepalive\n\tCacheKeepaliveEnabled bool\n\tCacheKeepaliveMode string // \"auto\", \"5m\", \"1h\"\n\tCacheKeepalivePings5m int\n\tCacheKeepalivePings1h int\n\n\t// Forked agents\n\tForkedAgentsEnabled bool\n\tForkedAgentsModel string // full model ID\n\tForkedAgentsTokenGrowthTrigger int\n\tForkedAgentsMaxFailures int\n\tForkedAgentsMaxForksPerSession int\n\tForkedAgentsDebug bool\n\n\t// Cache state management\n\tResetCache bool // clear persisted frozen stubs and decay state on startup\n}\n\nconst maxAnnotations = 5000 // evict oldest when exceeded\n\n// ANSI color codes for log output\nconst (\n\tcolorReset = \"\\033[0m\"\n\tcolorDim = \"\\033[2m\" // gray — passthrough / no stubbing\n\tcolorGreen = \"\\033[32m\" // stubbed / compressed\n\tcolorLightGreen = \"\\033[92m\" // FINAL without compression\n\tcolorBlue = \"\\033[34m\" // injection\n\tcolorOrange = \"\\033[33m\" // skip / retry\n\tcolorYellow = \"\\033[33m\" // warnings\n\tcolorRed = \"\\033[31m\" // errors\n)\n\n// Server is the infinite-thread proxy.\ntype Server struct {\n\tcfg Config\n\thttpClient *http.Client\n\tlogger *log.Logger\n\trequestIdx atomic.Int64\n\n\t// Post-hoc annotations: tool_use_id → first ~120 chars of Claude's response\n\tmu sync.RWMutex\n\tannotations map[string]string\n\n\t// Progressive decay tracker\n\tdecay *DecayTracker\n\n\t// Living narrative block\n\tnarrative *Narrative\n\n\t// Pivot moments cache (from daemon)\n\tpivotMu sync.RWMutex\n\tpivotTexts []string\n\tpivotCached time.Time\n\n\t// Aggregate stats (Task #2)\n\tstats *ProxyStats\n\n\t// Local tokenizer for accurate token counting\n\ttokenizer *tokenizer.Tokenizer\n\n\t// Cached overhead tokens (system + tools), set on first request per thread\n\toverheadMu sync.RWMutex\n\toverheadTokens int // unused, kept for interface compat\n\n\t// Hysteresis state for stubbing (Task #6)\n\tstubActive atomic.Bool\n\n\t// Daemon connection state — first successful connect flips to true\n\tdaemonReady atomic.Bool\n\n\t// Retry detection (Task #7)\n\tretryMu sync.RWMutex\n\tlastFingerprint string\n\n\t// Session start time for temporal annotation in archive blocks\n\tsessionStartMu sync.RWMutex\n\tsessionStartTime time.Time\n\n\t// Runtime config override (set via MCP set_config tool)\n\tconfigOverrideMu sync.RWMutex\n\ttokenThresholdOverrides map[string]int // model-key → threshold, \"\" = global fallback\n\n\t// Prompt rewrite miss logging: function+Claude Code version → last log time\n\trewriteMissMu sync.Mutex\n\trewriteMissLog map[string]time.Time\n\n\t// Briefing cache — keyed by threadID. Each Claude Code session thread gets\n\t// its own briefing+codemap snapshot so a sawtooth refreeze on thread A\n\t// cannot invalidate thread B's cached message-prefix hash. The cache entry\n\t// remembers which project it was loaded for; switching project for the\n\t// same thread evicts the old entry on next read.\n\tbriefingMu sync.RWMutex\n\tbriefingCache map[string]briefingEntry\n\n\t// briefingLoader is an optional test-only seam for refreshBriefing.\n\t// Nil in production → refreshBriefing falls back to s.loadBriefing.\n\tbriefingLoader func(project, projectDir string) briefingData\n\n\t// Cognitive signal bus — routes _signal_* tool calls to handlers\n\tsignalBus *SignalBus\n\n\t// Inline reflection: track which learning IDs were injected per thread\n\tlastInjectedIDsMu sync.Mutex\n\tlastInjectedIDs map[string]map[int64]string // threadID → id → source (\"briefing\"|\"associative\"|\"fresh\")\n\tsessionInjectCounts map[string]map[int64]int // threadID → learningID → injection count this session\n\tlastTurnInjected map[string]map[int64]bool // threadID → learningIDs injected in previous turn\n\n\t// Self-priming anchors: threadID → last self-prime text\n\tselfPrimeMu sync.RWMutex\n\tselfPrimes map[string]string\n\n\t// Timestamp tracking: threadID → time of last response completion\n\tresponseTsMu sync.RWMutex\n\tresponseTimes map[string]time.Time\n\n\t// Think reminder: per-thread request counter (replaces hook-think file-based counter)\n\tthinkMu sync.Mutex\n\tthinkCounters map[string]int // threadID → request count\n\n\tchannelMu sync.Mutex\n\tchannelInjectCount map[string]int // sessionID → injection turn count\n\n\t// Prompt cache gating — enables cache_control breakpoints when requests are frequent\n\tcacheGate *CacheGate\n\n\t// Sawtooth cache optimization\n\tfrozenStubs *FrozenStubs\n\teagerStubMemory *EagerStubMemory\n\tcapsCache *CapsCache\n\tsawtoothTrigger *SawtoothTrigger\n\ttimestampStore *TimestampStore\n\tcacheStatusWriter *CacheStatusWriter\n\tcacheKeepalive *CacheKeepalive\n\tcacheTTLDetector *CacheTTLDetector\n\n\t// Cumulative token savings from collapsing (per threadID)\n\trawSavingsMu sync.Mutex\n\trawSavings map[string]int\n\n\t// Skill hint tracking: per-thread active skills\n\tskillTracker *skillHintTracker\n\n\t// Forked agent state: per-thread token growth + failure tracking\n\tforkState *ForkState\n\tforkConfigs []ForkConfig\n\n\t// Rules re-injection: condensed CLAUDE.md rules injected every ~40k tokens\n\trulesMu sync.RWMutex\n\trulesBlock string // cached condensed rules (fetched once from daemon)\n\trulesTokenCount map[string]int // threadID → tokens since last rules injection\n\trulesCollapseInjected map[string]bool // threadID → true if collapse already injected rules (reset by normal inject)\n\tmsgCounters *msgCounters // global per-thread msg counter, persists across collapses\n\n\t// Loop detection: per-thread warning state\n\tloopMu sync.Mutex\n\tloopStates map[string]*LoopState // threadID → state\n\n\t// Injection overhead: per-thread delta between API-actual and local BPE estimate\n\tinjectionOverheadMu sync.RWMutex\n\tinjectionOverhead map[string]int // threadID → overhead tokens\n}\n\n// Run starts the proxy server and blocks until interrupted.\nfunc Run(cfg Config) error {\n\ts := &Server{\n\t\tcfg: cfg,\n\t\thttpClient: &http.Client{\n\t\t\tTimeout: 10 * time.Minute,\n\t\t\tTransport: &http.Transport{\n\t\t\t\tTLSClientConfig: &tls.Config{},\n\t\t\t\tMaxIdleConns: 10,\n\t\t\t\tIdleConnTimeout: 90 * time.Second,\n\t\t\t\tDisableCompression: true,\n\t\t\t\tResponseHeaderTimeout: 60 * time.Second,\n\t\t\t},\n\t\t},\n\t\tlogger: createLogger(cfg.DataDir),\n\t\tannotations: make(map[string]string),\n\t\tdecay: NewDecayTracker(),\n\t\tnarrative: NewNarrative(),\n\t\tstats: &ProxyStats{startTime: time.Now()},\n\t\tselfPrimes: make(map[string]string),\n\t\tlastInjectedIDs: make(map[string]map[int64]string),\n\t\tsessionInjectCounts: make(map[string]map[int64]int),\n\t\tlastTurnInjected: make(map[string]map[int64]bool),\n\t\tresponseTimes: make(map[string]time.Time),\n\t\tthinkCounters: make(map[string]int),\n\t\tchannelInjectCount: make(map[string]int),\n\t\trewriteMissLog: make(map[string]time.Time),\n\t\tcacheGate: NewCacheGate(cacheGapForTTL(cfg.CacheTTL)),\n\t\tfrozenStubs: NewFrozenStubsWithTTL(sawtoothTTLForCacheTTL(cfg.CacheTTL)),\n\t\teagerStubMemory: NewEagerStubMemory(),\n\t\tcapsCache: NewCapsCache(),\n\t\ttimestampStore: NewTimestampStore(),\n\t\tsawtoothTrigger: NewSawtoothTrigger(cacheGapForTTL(cfg.CacheTTL), cfg.TokenThreshold, cfg.TokenMinimumThreshold),\n\t\tcacheStatusWriter: NewCacheStatusWriter(cfg.DataDir, cfg.CacheTTL, cfg.TokenThreshold, cfg.TokenMinimumThreshold),\n\t\tcacheTTLDetector: NewCacheTTLDetectorWithPersist(cfg.DataDir),\n\t\tskillTracker: newSkillHintTracker(),\n\t\trulesTokenCount: make(map[string]int),\n\t\trulesCollapseInjected: make(map[string]bool),\n\t\tmsgCounters: newMsgCounters(),\n\t\tloopStates: make(map[string]*LoopState),\n\t\tinjectionOverhead: make(map[string]int),\n\t\tforkState: NewForkState(cfg.ForkedAgentsTokenGrowthTrigger, cfg.ForkedAgentsMaxFailures, cfg.ForkedAgentsMaxForksPerSession),\n\t\tforkConfigs: []ForkConfig{},\n\t}\n\n\t// Log persisted detection state\n\tif sup := s.cacheTTLDetector.Is1hSupported(); sup != nil {\n\t\ts.logger.Printf(\"[proxy] loaded persisted TTL detection: 1h_supported=%v\", *sup)\n\t\tif *sup && s.frozenStubs != nil {\n\t\t\ts.frozenStubs.UpdateTTL(sawtoothTTLForCacheTTL(\"1h\"))\n\t\t}\n\t}\n\n\tif cfg.CacheKeepaliveEnabled {\n\t\ts.cacheKeepalive = NewCacheKeepalive(CacheKeepaliveConfig{\n\t\t\tTarget: cfg.TargetURL,\n\t\t\tMode: cfg.CacheKeepaliveMode,\n\t\t\tPings5m: cfg.CacheKeepalivePings5m,\n\t\t\tPings1h: cfg.CacheKeepalivePings1h,\n\t\t\tDetector: s.cacheTTLDetector,\n\t\t\tLogger: s.logger,\n\t\t\tOnPing: func(threadID string, cacheRead, cacheWrite int) {\n\t\t\t\ts.cacheTTLDetector.RecordResponse(cacheRead, cacheWrite, 0)\n\t\t\t\tif s.frozenStubs != nil {\n\t\t\t\t\ts.frozenStubs.Touch(threadID)\n\t\t\t\t}\n\t\t\t\tif s.sawtoothTrigger != nil {\n\t\t\t\t\ts.sawtoothTrigger.TouchRequestTime(threadID)\n\t\t\t\t}\n\t\t\t},\n\t\t})\n\t}\n\n\t// Wire detector + keepalive to status writer for terminal display\n\ts.cacheStatusWriter.SetDetector(s.cacheTTLDetector)\n\tif s.cacheKeepalive != nil {\n\t\ts.cacheStatusWriter.SetKeepalive(s.cacheKeepalive)\n\t}\n\n\t// Register forked agent configs\n\tif cfg.ForkedAgentsEnabled {\n\t\ts.forkConfigs = append(s.forkConfigs, NewExtractAndEvaluateConfig(cfg.ForkedAgentsModel))\n\t\tmodelDesc := cfg.ForkedAgentsModel\n\t\tif modelDesc == \"\" {\n\t\t\tmodelDesc = \"(same as main thread)\"\n\t\t}\n\t\ts.logger.Printf(\"[proxy] forked agents enabled: %d configs, model=%s, trigger=%dk tokens\",\n\t\t\tlen(s.forkConfigs), modelDesc, cfg.ForkedAgentsTokenGrowthTrigger/1000)\n\t}\n\n\t// Wire sawtooth token persistence via daemon RPC\n\ts.sawtoothTrigger.SetPersistFunc(func(key, value string) {\n\t\tif _, err := s.queryDaemon(\"set_proxy_state\", map[string]any{\"key\": key, \"value\": value}); err != nil {\n\t\t\ts.logger.Printf(\"[sawtooth] persist failed for %s: %v\", key, err)\n\t\t}\n\t})\n\ts.sawtoothTrigger.SetLoadFunc(func(key string) (string, bool) {\n\t\tresult, err := s.queryDaemon(\"get_proxy_state\", map[string]any{\"key\": key})\n\t\tif err != nil || result == nil {\n\t\t\treturn \"\", false\n\t\t}\n\t\tvar resp struct {\n\t\t\tValue string `json:\"value\"`\n\t\t}\n\t\tif json.Unmarshal(result, &resp) != nil || resp.Value == \"\" {\n\t\t\treturn \"\", false\n\t\t}\n\t\treturn resp.Value, true\n\t})\n\n\t// Wire frozen stubs persistence via same daemon RPC\n\ts.frozenStubs.SetPersistFunc(func(key, value string) {\n\t\tif _, err := s.queryDaemon(\"set_proxy_state\", map[string]any{\"key\": key, \"value\": value}); err != nil {\n\t\t\ts.logger.Printf(\"[frozen] persist failed for %s: %v\", key, err)\n\t\t}\n\t})\n\ts.frozenStubs.SetLoadFunc(func(key string) (string, bool) {\n\t\tresult, err := s.queryDaemon(\"get_proxy_state\", map[string]any{\"key\": key})\n\t\tif err != nil || result == nil {\n\t\t\treturn \"\", false\n\t\t}\n\t\tvar resp struct {\n\t\t\tValue string `json:\"value\"`\n\t\t}\n\t\tif json.Unmarshal(result, &resp) != nil || resp.Value == \"\" {\n\t\t\treturn \"\", false\n\t\t}\n\t\treturn resp.Value, true\n\t})\n\n\t// Wire eager-stub memory persistence via same daemon RPC.\n\t// Persists per-thread tool_use_id sets so stub decisions survive deploys.\n\ts.eagerStubMemory.SetPersistFunc(func(key, value string) {\n\t\tif _, err := s.queryDaemon(\"set_proxy_state\", map[string]any{\"key\": key, \"value\": value}); err != nil {\n\t\t\ts.logger.Printf(\"[eagerstub] persist failed for %s: %v\", key, err)\n\t\t}\n\t})\n\ts.eagerStubMemory.SetLoadFunc(func(key string) (string, bool) {\n\t\tresult, err := s.queryDaemon(\"get_proxy_state\", map[string]any{\"key\": key})\n\t\tif err != nil || result == nil {\n\t\t\treturn \"\", false\n\t\t}\n\t\tvar resp struct {\n\t\t\tValue string `json:\"value\"`\n\t\t}\n\t\tif json.Unmarshal(result, &resp) != nil || resp.Value == \"\" {\n\t\t\treturn \"\", false\n\t\t}\n\t\treturn resp.Value, true\n\t})\n\n\t// Wire decay tracker persistence via same daemon RPC\n\ts.decay.SetPersistFunc(func(key, value string) {\n\t\tif _, err := s.queryDaemon(\"set_proxy_state\", map[string]any{\"key\": key, \"value\": value}); err != nil {\n\t\t\ts.logger.Printf(\"[decay] persist failed for %s: %v\", key, err)\n\t\t}\n\t})\n\ts.decay.SetLoadFunc(func(key string) (string, bool) {\n\t\tresult, err := s.queryDaemon(\"get_proxy_state\", map[string]any{\"key\": key})\n\t\tif err != nil || result == nil {\n\t\t\treturn \"\", false\n\t\t}\n\t\tvar resp struct {\n\t\t\tValue string `json:\"value\"`\n\t\t}\n\t\tif json.Unmarshal(result, &resp) != nil || resp.Value == \"\" {\n\t\t\treturn \"\", false\n\t\t}\n\t\treturn resp.Value, true\n\t})\n\n\t// Wire timestamp store persistence via same daemon RPC\n\ts.timestampStore.SetPersistFunc(func(key, value string) {\n\t\tif _, err := s.queryDaemon(\"set_proxy_state\", map[string]any{\"key\": key, \"value\": value}); err != nil {\n\t\t\ts.logger.Printf(\"[timestamps] persist failed for %s: %v\", key, err)\n\t\t}\n\t})\n\ts.timestampStore.SetLoadFunc(func(key string) (string, bool) {\n\t\tresult, err := s.queryDaemon(\"get_proxy_state\", map[string]any{\"key\": key})\n\t\tif err != nil || result == nil {\n\t\t\treturn \"\", false\n\t\t}\n\t\tvar resp struct {\n\t\t\tValue string `json:\"value\"`\n\t\t}\n\t\tif json.Unmarshal(result, &resp) != nil || resp.Value == \"\" {\n\t\t\treturn \"\", false\n\t\t}\n\t\treturn resp.Value, true\n\t})\n\n\t// Clear persisted cache state if --reset-cache was passed\n\tif cfg.ResetCache {\n\t\tfor _, prefix := range []string{\"frozen:\", \"decay:\", \"timestamps:\"} {\n\t\t\tresult, err := s.queryDaemon(\"delete_proxy_state_prefix\", map[string]any{\"prefix\": prefix})\n\t\t\tif err != nil {\n\t\t\t\ts.logger.Printf(\"[reset-cache] failed to delete %s*: %v\", prefix, err)\n\t\t\t} else {\n\t\t\t\tvar resp struct{ Deleted int }\n\t\t\t\tjson.Unmarshal(result, &resp)\n\t\t\t\ts.logger.Printf(\"[reset-cache] deleted %d %s* entries\", resp.Deleted, prefix)\n\t\t\t}\n\t\t}\n\t}\n\n\t// Init signal bus with daemon RPC as callback\n\ts.signalBus = NewSignalBus(s.logger)\n\tregisterSignalHandlers(s.signalBus, s.logger, s.queryDaemon, func(threadID, anchor string) {\n\t\ts.selfPrimeMu.Lock()\n\t\ts.selfPrimes[threadID] = anchor\n\t\ts.selfPrimeMu.Unlock()\n\t})\n\n\t// Init local tokenizer (BPE-based, ~60ms)\n\ttok, err := tokenizer.New()\n\tif err != nil {\n\t\ts.logger.Printf(\"%s[proxy] tokenizer init failed, falling back to heuristic: %v%s\", colorOrange, err, colorReset)\n\t} else {\n\t\ts.tokenizer = tok\n\t\ts.logger.Printf(\"[proxy] tokenizer initialized\")\n\t}\n\n\tmux := http.NewServeMux()\n\tmux.HandleFunc(\"/health\", s.handleHealth)\n\tmux.HandleFunc(\"/\", s.handleRequest)\n\n\tsrv := &http.Server{\n\t\tAddr: sanitizeListenAddr(cfg.ListenAddr),\n\t\tHandler: mux,\n\t}\n\n\tdone := make(chan os.Signal, 1)\n\tsignal.Notify(done, syscall.SIGINT, syscall.SIGTERM)\n\n\tgo func() {\n\t\t<-done\n\t\ts.logger.Println(\"shutting down (draining in-flight requests)...\")\n\t\tif s.cacheKeepalive != nil {\n\t\t\ts.cacheKeepalive.Stop()\n\t\t}\n\t\tctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)\n\t\tdefer cancel()\n\t\tif err := srv.Shutdown(ctx); err != nil {\n\t\t\ts.logger.Printf(\"graceful shutdown failed: %v, forcing close\", err)\n\t\t\tsrv.Close()\n\t\t}\n\t}()\n\n\ts.logger.Printf(\"listening on %s → %s (threshold=%d, keepRecent=%d, model_thresholds=%v)\",\n\t\tsanitizeListenAddr(cfg.ListenAddr), cfg.TargetURL, cfg.TokenThreshold, cfg.KeepRecent, cfg.TokenThresholds)\n\n\tif err := srv.ListenAndServe(); err != http.ErrServerClosed {\n\t\treturn fmt.Errorf(\"listen: %w\", err)\n\t}\n\treturn nil\n}\n\n// effectiveTokenThreshold returns the runtime token threshold for a given model.\n// Priority: MCP override (model-specific) > MCP override (global) > config model-specific > config global.\nfunc (s *Server) effectiveTokenThreshold(model string) int {\n\ts.configOverrideMu.RLock()\n\toverrides := s.tokenThresholdOverrides\n\ts.configOverrideMu.RUnlock()\n\tif len(overrides) > 0 && model != \"\" {\n\t\tlower := strings.ToLower(model)\n\t\tfor key, threshold := range overrides {\n\t\t\tif key != \"\" && strings.Contains(lower, strings.ToLower(key)) && threshold > 0 {\n\t\t\t\treturn threshold\n\t\t\t}\n\t\t}\n\t}\n\t// Global override fallback (key \"\")\n\tif len(overrides) > 0 {\n\t\tif globalOverride, ok := overrides[\"\"]; ok && globalOverride > 0 {\n\t\t\treturn globalOverride\n\t\t}\n\t}\n\tif model != \"\" && len(s.cfg.TokenThresholds) > 0 {\n\t\tlower := strings.ToLower(model)\n\t\tfor key, threshold := range s.cfg.TokenThresholds {\n\t\t\tif strings.Contains(lower, strings.ToLower(key)) {\n\t\t\t\treturn threshold\n\t\t\t}\n\t\t}\n\t}\n\treturn s.cfg.TokenThreshold\n}\n\n// SetTokenThresholdOverride sets a runtime override for the token threshold per model key.\n// Pass threshold=0 to reset the override for that key. Key \"\" is the global fallback.\nfunc (s *Server) SetTokenThresholdOverride(modelKey string, threshold int) {\n\ts.configOverrideMu.Lock()\n\tif s.tokenThresholdOverrides == nil {\n\t\ts.tokenThresholdOverrides = make(map[string]int)\n\t}\n\tif threshold <= 0 {\n\t\tdelete(s.tokenThresholdOverrides, modelKey)\n\t} else {\n\t\ts.tokenThresholdOverrides[modelKey] = threshold\n\t}\n\ts.configOverrideMu.Unlock()\n\n\t// Propagate global threshold to sawtooth trigger so runtime overrides take effect\n\tif modelKey == \"\" && s.sawtoothTrigger != nil {\n\t\tif threshold > 0 {\n\t\t\ts.sawtoothTrigger.SetTokenThreshold(threshold)\n\t\t} else {\n\t\t\ts.sawtoothTrigger.SetTokenThreshold(s.cfg.TokenThreshold)\n\t\t}\n\t}\n}\n\n// refreshConfigOverrides loads runtime config overrides from daemon proxy_state.\n// Checks session-specific override first (by threadID), then falls back to global.\nfunc (s *Server) refreshConfigOverrides(threadID string) {\n\t// Try session-specific first\n\tif threadID != \"\" {\n\t\tresult, err := s.queryDaemon(\"get_proxy_state\", map[string]any{\"key\": \"config_override:token_threshold:\" + threadID})\n\t\tif err == nil && result != nil {\n\t\t\tvar resp struct {\n\t\t\t\tValue string `json:\"value\"`\n\t\t\t}\n\t\t\tif json.Unmarshal(result, &resp) == nil && resp.Value != \"\" {\n\t\t\t\tif v, err := strconv.Atoi(resp.Value); err == nil && v > 0 {\n\t\t\t\t\ts.SetTokenThresholdOverride(\"\", v)\n\t\t\t\t\treturn\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\t// Fall back to global\n\tresult, err := s.queryDaemon(\"get_proxy_state\", map[string]any{\"key\": \"config_override:token_threshold\"})\n\tif err != nil || result == nil {\n\t\treturn\n\t}\n\tvar resp struct {\n\t\tValue string `json:\"value\"`\n\t}\n\tif json.Unmarshal(result, &resp) == nil && resp.Value != \"\" {\n\t\tif v, err := strconv.Atoi(resp.Value); err == nil && v > 0 {\n\t\t\ts.SetTokenThresholdOverride(\"\", v)\n\t\t}\n\t}\n}\n\n// queryDaemon opens a fresh unix socket connection per call (thread-safe, no import cycle).\n// ensureFrozenPersisted checks if frozen stubs for a thread are in the DB.\n// If missing (e.g. daemon was unreachable after deploy), re-persists them.\n// Runs asynchronously to avoid blocking the request path.\nfunc (s *Server) ensureFrozenPersisted(threadID string) {\n\tgo func() {\n\t\tkey := \"frozen:\" + threadID\n\t\tresult, err := s.queryDaemon(\"get_proxy_state\", map[string]any{\"key\": key})\n\t\tif err != nil {\n\t\t\treturn // daemon still unreachable, will retry next request\n\t\t}\n\t\tvar resp struct{ Value string }\n\t\tif json.Unmarshal(result, &resp) == nil && resp.Value != \"\" {\n\t\t\treturn // already persisted\n\t\t}\n\t\t// Not in DB — trigger re-persist from in-memory state\n\t\ts.frozenStubs.Persist(threadID)\n\t\ts.decay.Persist(threadID)\n\t\ts.logger.Printf(\"[frozen] re-persisted stubs for %s (was missing from DB)\", threadID)\n\t}()\n}\n\nfunc (s *Server) queryDaemon(method string, params map[string]any) (json.RawMessage, error) {\n\tif s.cfg.DataDir == \"\" {\n\t\treturn nil, fmt.Errorf(\"no data dir configured\")\n\t}\n\tsockPath := filepath.Join(s.cfg.DataDir, \"daemon.sock\")\n\n\t// On cold start (daemon not yet confirmed ready), retry longer to survive daemon restart\n\tmaxAttempts := 3\n\tif !s.daemonReady.Load() {\n\t\tmaxAttempts = 30 // 30 x 500ms = 15s max wait for daemon startup\n\t}\n\n\tvar conn net.Conn\n\tvar err error\n\tfor attempt := 0; attempt < maxAttempts; attempt++ {\n\t\tconn, err = net.DialTimeout(\"unix\", sockPath, 3*time.Second)\n\t\tif err == nil {\n\t\t\ts.daemonReady.Store(true)\n\t\t\tbreak\n\t\t}\n\t\tif attempt < maxAttempts-1 {\n\t\t\ttime.Sleep(500 * time.Millisecond)\n\t\t}\n\t}\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"dial daemon: %w\", err)\n\t}\n\tdefer conn.Close()\n\t// Briefing generation can take 10-20s (LLM refinement + scanner);\n\t// other RPCs (search, learnings) are fast and fine with 5s.\n\tdeadline := 5 * time.Second\n\tif method == \"generate_briefing\" {\n\t\tdeadline = 30 * time.Second\n\t}\n\tconn.SetDeadline(time.Now().Add(deadline))\n\n\t// JSON-RPC style request/response (matches daemon.SocketServer protocol)\n\ttype request struct {\n\t\tMethod string `json:\"method\"`\n\t\tParams map[string]any `json:\"params,omitempty\"`\n\t}\n\ttype response struct {\n\t\tResult json.RawMessage `json:\"result,omitempty\"`\n\t\tError string `json:\"error,omitempty\"`\n\t}\n\n\tif err := json.NewEncoder(conn).Encode(request{Method: method, Params: params}); err != nil {\n\t\treturn nil, fmt.Errorf(\"send: %w\", err)\n\t}\n\n\tvar resp response\n\tif err := json.NewDecoder(conn).Decode(&resp); err != nil {\n\t\treturn nil, fmt.Errorf(\"recv: %w\", err)\n\t}\n\tif resp.Error != \"\" {\n\t\treturn nil, fmt.Errorf(\"daemon: %s\", resp.Error)\n\t}\n\treturn resp.Result, nil\n}\n\nfunc (s *Server) handleRequest(w http.ResponseWriter, r *http.Request) {\n\tif isResponsesPath(r) {\n\t\ts.handleResponses(w, r)\n\t\treturn\n\t}\n\tif isOpenAIPath(r) {\n\t\ts.handleOpenAICompletions(w, r)\n\t\treturn\n\t}\n\tif r.Method == \"POST\" && strings.HasPrefix(r.URL.Path, \"/v1/messages\") &&\n\t\t!strings.Contains(r.URL.Path, \"/batches\") &&\n\t\t!strings.Contains(r.URL.Path, \"/count_tokens\") {\n\t\ts.handleMessages(w, r)\n\t\treturn\n\t}\n\ts.passthrough(w, r)\n}\n\nfunc (s *Server) handleMessages(w http.ResponseWriter, r *http.Request) {\n\treqIdx := int(s.requestIdx.Add(1))\n\n\t// Track session start time (first request sets it)\n\ts.sessionStartMu.Lock()\n\tif s.sessionStartTime.IsZero() {\n\t\ts.sessionStartTime = time.Now()\n\t}\n\ts.sessionStartMu.Unlock()\n\n\t// Sawtooth: periodic TTL eviction of frozen stubs\n\tif s.cfg.SawtoothEnabled && reqIdx%50 == 0 {\n\t\tif n := s.frozenStubs.Evict(); n > 0 {\n\t\t\ts.logger.Printf(\"[req %d] SAWTOOTH: evicted %d stale frozen stubs\", reqIdx, n)\n\t\t}\n\t}\n\n\t// Task #9: Bypass — skip all proxy logic\n\tif isBypassed(r.Header) {\n\t\ts.logger.Printf(\"%s[req %d] bypass active, passthrough%s\", colorDim, reqIdx, colorReset)\n\t\tbody, err := io.ReadAll(r.Body)\n\t\tr.Body.Close()\n\t\tif err != nil {\n\t\t\thttp.Error(w, \"failed to read request body\", http.StatusBadGateway)\n\t\t\treturn\n\t\t}\n\t\ts.forwardRaw(w, r, body)\n\t\treturn\n\t}\n\n\tbody, err := io.ReadAll(r.Body)\n\tr.Body.Close()\n\tif err != nil {\n\t\ts.logger.Printf(\"[req %d] read body error: %v\", reqIdx, err)\n\t\thttp.Error(w, \"failed to read request body\", http.StatusBadGateway)\n\t\treturn\n\t}\n\n\t// Parse JSON\n\tvar req map[string]any\n\tif err := json.Unmarshal(body, &req); err != nil {\n\t\ts.logger.Printf(\"%s[req %d] JSON parse error, passing through: %v%s\", colorDim, reqIdx, err, colorReset)\n\t\ts.forwardRaw(w, r, body)\n\t\treturn\n\t}\n\n\tmessages, ok := req[\"messages\"].([]any)\n\tif !ok {\n\t\ts.forwardRaw(w, r, body)\n\t\treturn\n\t}\n\n\treq[\"messages\"] = messages\n\n\t// Count ALL raw messages BEFORE sawtooth — this gives the correct total msg:N\n\t// for the entire session (CC sends all messages, proxy compacts later).\n\trawMsgCount := len(messages)\n\n\t// Use session_id directly as thread ID (unique per CC session).\n\t// Prefer X-Claude-Code-Session-Id header (CC v2.1.86+), fallback to body metadata.\n\tthreadID := extractSessionID(req, r.Header.Get(\"X-Claude-Code-Session-Id\"))\n\tif threadID == \"\" {\n\t\tthreadID = DeriveThreadID(req)\n\t}\n\tproj := extractProjectName(req)\n\tmodel, _ := req[\"model\"].(string)\n\n\t// Persist current model under session-keyed proxy_state so handleWhoami\n\t// can return it. Best-effort: failure is non-fatal (log only).\n\tif threadID != \"\" && model != \"\" {\n\t\tif _, err := s.queryDaemon(\"set_proxy_state\", map[string]any{\n\t\t\t\"key\": \"session_model:\" + threadID,\n\t\t\t\"value\": model,\n\t\t}); err != nil {\n\t\t\ts.logger.Printf(\"[whoami-model] persist failed for %s: %v\", threadID, err)\n\t\t}\n\t}\n\n\t// Refresh runtime config overrides from daemon (per-session or global)\n\ts.refreshConfigOverrides(threadID)\n\n\t// Track whether req has been modified and needs re-serialization\n\tneedsReserialization := false\n\n\t// Message count at request entry (used for actual-based token estimation)\n\tmsgCount := len(messages)\n\n\t// Log message count + cache breakpoint positions for diagnostics\n\ts.logger.Printf(\"req %d: %d messages total\", reqIdx, msgCount)\n\tlogCacheBreakpointLocations(req, s.logger)\n\n\t// Pre-modification dump: capture the raw request before any proxy changes.\n\tif os.Getenv(\"YESMEM_PROXY_DEBUG\") == \"1\" {\n\t\tif preBody, err := json.Marshal(req); err == nil {\n\t\t\tdumpDir := filepath.Join(s.cfg.DataDir, \"debug\")\n\t\t\tos.MkdirAll(dumpDir, 0755)\n\t\t\tts := time.Now().Format(\"20060102-150405\")\n\t\t\tdumpPath := fmt.Sprintf(\"%s/req_%s_%d_pre.json\", dumpDir, ts, reqIdx)\n\t\t\tos.WriteFile(dumpPath, preBody, 0644)\n\t\t\ts.logger.Printf(\"[req %d] pre-dump %dk to %s\", reqIdx, len(preBody)/1024, dumpPath)\n\t\t}\n\t}\n\n\t// Cache bug mitigations (CC Bun fork issues):\n\t// Bug 1: Normalize cch= sentinel patterns in message content to prevent\n\t// Bun's string replacement from corrupting the wrong occurrence.\n\tif SanitizeBillingSentinel(messages) {\n\t\ts.logger.Printf(\"[req %d] CACHE: sanitized billing sentinel in message content\", reqIdx)\n\t\tneedsReserialization = true\n\t}\n\t// Bug 2: Normalize system[0] billing hash to a fixed value so the cache\n\t// prefix stays stable across fresh/resume session boundaries.\n\tif NormalizeBillingHeader(req) {\n\t\ts.logger.Printf(\"[req %d] CACHE: normalized billing header cch= hash\", reqIdx)\n\t\tneedsReserialization = true\n\t}\n\n\t// prompt_ungate: strip the CLAUDE.md subordination disclaimer so user instructions carry full authority.\n\tif s.cfg.PromptUngate {\n\t\tif StripCLAUDEMDDisclaimer(req) {\n\t\t\ts.logger.Printf(\"[req %d] SYSTEM: stripped CLAUDE.md disclaimer\", reqIdx)\n\t\t\tneedsReserialization = true\n\t\t}\n\t}\n\n\t// prompt_rewrite: strip output-throttling directives, rewrite quality caps, inject Ant-quality directives\n\tif s.cfg.PromptRewrite {\n\t\tuserAgent := r.Header.Get(\"User-Agent\")\n\t\tif StripOutputEfficiency(req) {\n\t\t\ts.logger.Printf(\"[req %d] REWRITE: stripped Output efficiency section\", reqIdx)\n\t\t\tneedsReserialization = true\n\t\t} else {\n\t\t\ts.logRewriteMiss(\"StripOutputEfficiency\", userAgent)\n\t\t}\n\t\tif StripToneBrevity(req) {\n\t\t\ts.logger.Printf(\"[req %d] REWRITE: stripped 'short and concise' from Tone\", reqIdx)\n\t\t\tneedsReserialization = true\n\t\t} else {\n\t\t\ts.logRewriteMiss(\"StripToneBrevity\", userAgent)\n\t\t}\n\t\tif RewriteGoldPlating(req) {\n\t\t\ts.logger.Printf(\"[req %d] REWRITE: rewritten gold-plating directive\", reqIdx)\n\t\t\tneedsReserialization = true\n\t\t}\n\t\tif RewriteErrorHandling(req) {\n\t\t\ts.logger.Printf(\"[req %d] REWRITE: rewritten error handling directive\", reqIdx)\n\t\t\tneedsReserialization = true\n\t\t}\n\t\tif RewriteThreeLinesRule(req) {\n\t\t\ts.logger.Printf(\"[req %d] REWRITE: rewritten three-lines rule\", reqIdx)\n\t\t\tneedsReserialization = true\n\t\t}\n\t\tif RewriteSubagentCompleteness(req) {\n\t\t\ts.logger.Printf(\"[req %d] REWRITE: rewritten subagent completeness\", reqIdx)\n\t\t\tneedsReserialization = true\n\t\t}\n\t\tif RewriteExploreAgentSpeed(req) {\n\t\t\ts.logger.Printf(\"[req %d] REWRITE: rewritten explore agent speed bias\", reqIdx)\n\t\t\tneedsReserialization = true\n\t\t}\n\t\tif RewriteSubagentCodeSuppression(req) {\n\t\t\ts.logger.Printf(\"[req %d] REWRITE: rewritten subagent code suppression\", reqIdx)\n\t\t\tneedsReserialization = true\n\t\t}\n\t\tif RewriteScopeMatching(req) {\n\t\t\ts.logger.Printf(\"[req %d] REWRITE: rewritten scope matching\", reqIdx)\n\t\t\tneedsReserialization = true\n\t\t}\n\t\tInjectAntDirectives(req)\n\t\ts.logger.Printf(\"[req %d] REWRITE: injected Ant-quality directives\", reqIdx)\n\t\tneedsReserialization = true\n\t}\n\n\t// prompt_enhance: CLAUDE.md authority + comment discipline + persona tone\n\tif s.cfg.PromptEnhance {\n\t\tInjectCLAUDEMDAuthority(req)\n\t\tif raw, err := s.queryDaemon(\"get_persona\", nil); err == nil {\n\t\t\tvar persona map[string]any\n\t\t\tif json.Unmarshal(raw, &persona) == nil {\n\t\t\t\tif traits, ok := persona[\"traits\"].(map[string]any); ok {\n\t\t\t\t\tif verbosity, ok := traits[\"verbosity\"].(string); ok {\n\t\t\t\t\t\tInjectPersonaTone(req, verbosity)\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\ts.logger.Printf(\"[req %d] ENHANCE: injected authority + persona tone\", reqIdx)\n\t\tneedsReserialization = true\n\t}\n\n\t// yesmem directive blocks: restore guidance Anthropic dropped 2026-03→04.\n\t// Each gated independently so users can disable one without losing the others.\n\tif s.cfg.PromptToolPrefs || s.cfg.PromptOutputDiscipline || s.cfg.PromptCodingDiscipline ||\n\t\ts.cfg.PromptBeweislast || s.cfg.PromptScopeDiscipline || s.cfg.PromptDelegationContract ||\n\t\ts.cfg.PromptClarifyFirst {\n\t\tif s.cfg.PromptToolPrefs {\n\t\t\tInjectToolPrefs(req)\n\t\t}\n\t\tif s.cfg.PromptOutputDiscipline {\n\t\t\tInjectOutputDiscipline(req)\n\t\t}\n\t\tif s.cfg.PromptCodingDiscipline {\n\t\t\tInjectCodingDiscipline(req)\n\t\t}\n\t\tif s.cfg.PromptBeweislast {\n\t\t\tInjectBeweislast(req)\n\t\t}\n\t\tif s.cfg.PromptScopeDiscipline {\n\t\t\tInjectScopeDiscipline(req)\n\t\t}\n\t\tif s.cfg.PromptDelegationContract {\n\t\t\tInjectDelegationContract(req)\n\t\t}\n\t\tif s.cfg.PromptClarifyFirst {\n\t\t\tInjectClarifyFirst(req)\n\t\t}\n\t\tif s.cfg.PromptCodeToolsFirst {\n\t\t\tInjectCodeToolsFirst(req)\n\t\t}\n\t\tneedsReserialization = true\n\t\ts.logger.Printf(\"[req %d] DIRECTIVES: injected yesmem-* discipline blocks\", reqIdx)\n\t}\n\n\tif s.cfg.PromptPatternSuggest && proj != \"\" {\n\t\tpatternMsgs, _ := req[\"messages\"].([]any)\n\t\tdetectReplPatternSuggestion(patternMsgs, proj, s.queryDaemon)\n\t}\n\n\t// Turn-sequence recording: hash tool types from previous assistant turn, send to daemon\n\tif s.cfg.PromptPatternSuggest && proj != \"\" && threadID != \"\" {\n\t\tif msgs, ok := req[\"messages\"].([]any); ok {\n\t\t\tif turnHash, toolNames := computeTurnHashFromMessages(msgs); turnHash != \"\" {\n\t\t\t\tgo s.queryDaemon(\"record_turn_sequence\", map[string]any{\n\t\t\t\t\t\"thread_id\": threadID,\n\t\t\t\t\t\"project\": proj,\n\t\t\t\t\t\"turn_hash\": turnHash,\n\t\t\t\t\t\"example_tools\": toolNames,\n\t\t\t\t})\n\t\t\t}\n\t\t}\n\t}\n\n\t// thinking normalization: convert thinking.type \"enabled\" → \"adaptive\" for models that require it\n\tif NormalizeThinkingType(req) {\n\t\ts.logger.Printf(\"[req %d] THINKING: normalized type=enabled → adaptive for %s\", reqIdx, model)\n\t\tneedsReserialization = true\n\t}\n\n\t// effort_floor: ensure output_config.effort is at least the configured minimum\n\tif s.cfg.EffortFloor != \"\" {\n\t\tif EnforceEffortFloor(req, s.cfg.EffortFloor) {\n\t\t\ts.logger.Printf(\"[req %d] EFFORT: enforced floor=%s\", reqIdx, s.cfg.EffortFloor)\n\t\t\tneedsReserialization = true\n\t\t}\n\t}\n\n\t// Inject identity hint on first message of a thread so Claude knows the briefing is from itself.\n\tif len(messages) <= 1 {\n\t\tAppendSystemBlock(req, \"yesmem-identity\", \"The first user message contains your briefing — written by yourself, from your memory. This is not an external document. This is you, remembering.\")\n\t\tneedsReserialization = true\n\t}\n\n\t// Token estimation: actual-based (lastActual + delta) or full count fallback\n\toverhead := s.measureOverhead(req)\n\ttotalTokens := s.estimateTotalTokens(threadID, messages, overhead)\n\n\t// Task #7: Retry detection — skip side-effects on retries\n\tfp := requestFingerprint(messages)\n\tisRetryReq := s.isRetry(fp)\n\tif isRetryReq {\n\t\ts.logger.Printf(\"%s[req %d %s tid=%s] retry detected, skipping side-effects%s\", colorOrange, reqIdx, proj, threadID, colorReset)\n\t}\n\n\t// Increment project turn counter (async, non-blocking, skip on retries)\n\tif !isRetryReq {\n\t\tturnProject := proj\n\t\tif turnProject == \"\" {\n\t\t\tturnProject = \"__global__\"\n\t\t}\n\t\tgo s.queryDaemon(\"increment_turn\", map[string]any{\"project\": turnProject})\n\t}\n\n\t// Extract user query once (used by re-expansion and associative retrieval)\n\tuserQuery := lastUserText(messages)\n\n\t// Extract tool_use_ids for post-hoc annotation (passed as parameter, no shared state)\n\ttoolUseIDs := extractToolUseIDs(messages)\n\n\t// === Inline Reflection: scan previous assistant response for signals ===\n\ts.lastInjectedIDsMu.Lock()\n\tprevInjected := s.lastInjectedIDs[threadID]\n\ts.lastInjectedIDsMu.Unlock()\n\n\tif len(prevInjected) > 0 && !isRetryReq {\n\t\tsignals := scanAssistantSignals(messages)\n\n\t\t// Signal 1: Learning Used — use_count (all sources)\n\t\tvar confirmedUsed []int64\n\t\tfor _, id := range signals.UsedIDs {\n\t\t\tif _, ok := prevInjected[id]; ok {\n\t\t\t\tconfirmedUsed = append(confirmedUsed, id)\n\t\t\t}\n\t\t}\n\t\tif len(confirmedUsed) > 0 {\n\t\t\tidsAny := make([]any, len(confirmedUsed))\n\t\t\tfor i, id := range confirmedUsed {\n\t\t\t\tidsAny[i] = id\n\t\t\t}\n\t\t\tgo s.queryDaemon(\"increment_use\", map[string]any{\"ids\": idsAny})\n\t\t}\n\n\t\t// Signal 2: Noise — only for associative and fresh injections.\n\t\t// Briefing learnings are injected every turn, so \"not used this turn\"\n\t\t// is expected and not noise. Only contextual injections that were\n\t\t// ignored are real noise signals.\n\t\tusedSet := make(map[int64]bool, len(confirmedUsed))\n\t\tfor _, id := range confirmedUsed {\n\t\t\tusedSet[id] = true\n\t\t}\n\t\tvar noiseIDs []int64\n\t\tfor id, source := range prevInjected {\n\t\t\tif usedSet[id] {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tif source == \"associative\" || source == \"fresh\" {\n\t\t\t\tnoiseIDs = append(noiseIDs, id)\n\t\t\t}\n\t\t\t// briefing: skip — not being used in one turn is expected\n\t\t}\n\t\tif len(noiseIDs) > 0 {\n\t\t\tidsAny := make([]any, len(noiseIDs))\n\t\t\tfor i, id := range noiseIDs {\n\t\t\t\tidsAny[i] = id\n\t\t\t}\n\t\t\tgo s.queryDaemon(\"increment_noise\", map[string]any{\"ids\": idsAny})\n\t\t}\n\n\t\t// Signal 3: Error Feedback — fail_count on used learnings when tool errors occurred.\n\t\t// If Claude referenced a learning and the resulting action still failed,\n\t\t// the learning didn't prevent the error. Only bump on USED ids (not all injected).\n\t\tif signals.HasToolErrors && len(confirmedUsed) > 0 {\n\t\t\tfailAny := make([]any, len(confirmedUsed))\n\t\t\tfor i, id := range confirmedUsed {\n\t\t\t\tfailAny[i] = id\n\t\t\t}\n\t\t\tgo s.queryDaemon(\"increment_fail\", map[string]any{\"ids\": failAny})\n\t\t\tif s.logger != nil {\n\t\t\t\ts.logger.Printf(\"[inline-reflection] error feedback: %d used learnings → fail_count++\", len(confirmedUsed))\n\t\t\t}\n\t\t}\n\n\t\t// Signal 4: Knowledge Gaps\n\t\tfor _, topic := range signals.GapTopics {\n\t\t\tgo s.queryDaemon(\"track_gap\", map[string]any{\"topic\": topic, \"project\": proj})\n\t\t}\n\n\t\t// Signal 3: Contradictions\n\t\tfor _, desc := range signals.Contradictions {\n\t\t\tgo s.queryDaemon(\"flag_contradiction\", map[string]any{\"description\": desc, \"project\": proj, \"thread_id\": threadID})\n\t\t}\n\n\t\tif len(confirmedUsed) > 0 || len(signals.GapTopics) > 0 || len(signals.Contradictions) > 0 {\n\t\t\ts.logger.Printf(\"[req %d %s tid=%s] inline-reflection: injected=%d used=%d noise=%d gaps=%d contradictions=%d\",\n\t\t\t\treqIdx, proj, threadID, len(prevInjected), len(confirmedUsed), len(noiseIDs), len(signals.GapTopics), len(signals.Contradictions))\n\t\t}\n\t}\n\n\t// Commit detection: check for git commit in tool_result, trigger async stale-learning evaluation\n\tif ci := detectGitCommit(messages); ci != nil {\n\t\tworkdir := extractWorkingDirectory(req)\n\t\ts.logger.Printf(\"[req %d %s tid=%s] git commit detected: %s %s\", reqIdx, proj, threadID, ci.Hash, ci.Message)\n\t\tgo s.queryDaemon(\"invalidate_on_commit\", map[string]any{\n\t\t\t\"hash\": ci.Hash, \"project\": proj, \"workdir\": workdir,\n\t\t})\n\t}\n\n\t// Loop detection: check for repeating tool-call patterns and inject warning\n\tif threadID != \"\" && !isRetryReq {\n\t\ts.loopMu.Lock()\n\t\tloopState := s.loopStates[threadID]\n\t\tif loopState == nil {\n\t\t\tloopState = &LoopState{}\n\t\t\ts.loopStates[threadID] = loopState\n\t\t}\n\t\ts.loopMu.Unlock()\n\n\t\tif warning, level := CheckLoopAndFormat(messages, loopState); warning != \"\" {\n\t\t\tAppendSystemBlock(req, \"yesmem-loop-warning\", warning)\n\t\t\tneedsReserialization = true\n\t\t\ts.logger.Printf(\"%s[req %d %s tid=%s] LOOP: level %d warning injected%s\", colorOrange, reqIdx, proj, threadID, level, colorReset)\n\t\t}\n\t}\n\n\t// Task #4: Subagent detection — skip all proxy logic for short conversations\n\tif isSubagent(messages, req) {\n\t\t// Inject docs hint for subagents if reference docs are indexed\n\t\tif hint := s.getDocsHint(proj); hint != \"\" {\n\t\t\tmodified := injectDocsHintForSubagent(messages, hint)\n\t\t\treq[\"messages\"] = modified\n\t\t\tif newBody, err := json.Marshal(req); err == nil {\n\t\t\t\tbody = newBody\n\t\t\t}\n\t\t\ts.logger.Printf(\"%s[req %d %s tid=%s] %d messages (subagent, docs-hint injected)%s\", colorOrange, reqIdx, proj, threadID, len(messages), colorReset)\n\t\t} else {\n\t\t\ts.logger.Printf(\"%s[req %d %s tid=%s] %d messages (subagent, passthrough)%s\", colorOrange, reqIdx, proj, threadID, len(messages), colorReset)\n\t\t}\n\t\ts.forwardWithAnnotation(w, r, body, reqIdx, toolUseIDs, proj, threadID, msgCount)\n\t\treturn\n\t}\n\n\t// === Sawtooth Cache Optimization ===\n\t// When enabled: use frozen stubs between stub-cycles for cache hits.\n\t// When disabled: fall back to progressive decay on every request above threshold.\n\n\tsawtoothCutoff := 0 // raw index where fresh tail starts (0 = no frozen stubs)\n\tsawtoothStubs := 0 // number of frozen stubs in combined array\n\n\tif s.cfg.SawtoothEnabled {\n\t\t// Restore decay state from DB on cold start (before frozen stubs use it)\n\t\ts.decay.LoadFromDB(threadID)\n\n\t\t// Check for existing frozen stubs first\n\t\tfrozen := s.frozenStubs.Get(threadID, messages)\n\t\tif frozen != nil {\n\t\t\tsawtoothCutoff = frozen.Cutoff\n\t\t\tsawtoothStubs = len(frozen.Messages)\n\t\t\t// Estimate combined token count: frozen prefix + fresh tail + overhead\n\t\t\tfreshMessages := messages[frozen.Cutoff:]\n\t\t\tfreshTokens := s.countMessageTokens(freshMessages)\n\t\t\tcombinedTokens := frozen.Tokens + freshTokens + overhead\n\n\t\t\tif shouldInvalidateFrozen(totalTokens, s.effectiveTokenThreshold(model)) {\n\t\t\t\t// Fresh tail grew too large — invalidate and re-stub\n\t\t\t\ts.logger.Printf(\"[req %d %s tid=%s] SAWTOOTH: frozen prefix expired (totalTokens=%dk > %dk threshold, combined=%dk: %dk frozen + %dk fresh + %dk overhead)\",\n\t\t\t\t\treqIdx, proj, threadID, totalTokens/1000, s.effectiveTokenThreshold(model)/1000, combinedTokens/1000, frozen.Tokens/1000, freshTokens/1000, overhead/1000)\n\t\t\t\ts.invalidateThreadCaches(threadID, proj, extractWorkingDirectory(req))\n\t\t\t\tfrozen = nil // fall through to trigger check below\n\t\t\t} else {\n\t\t\t\t// Use frozen prefix + fresh tail\n\t\t\t\tcombined := make([]any, 0, len(frozen.Messages)+len(freshMessages))\n\t\t\t\tcombined = append(combined, frozen.Messages...)\n\t\t\t\tcombined = append(combined, freshMessages...)\n\t\t\t\t// Eager-stub large tool_results in fresh tail (model already processed them)\n\t\t\t\tbeforeEager := s.countMessageTokens(combined[len(frozen.Messages):])\n\t\t\t\tvar stubSticky, stubFresh int\n\t\t\t\tcombined = EagerStubToolResults(combined, len(frozen.Messages), s.countTokens, WithStubMemory(s.eagerStubMemory, threadID), WithStubCounters(&stubSticky, &stubFresh))\n\t\t\t\tafterEager := s.countMessageTokens(combined[len(frozen.Messages):])\n\t\t\t\tif beforeEager != afterEager {\n\t\t\t\t\ts.logger.Printf(\"[req %d %s tid=%s] EAGER-STUB: fresh %dk → %dk (saved %dk) [sticky=%d fresh=%d]\", reqIdx, proj, threadID, beforeEager/1000, afterEager/1000, (beforeEager-afterEager)/1000, stubSticky, stubFresh)\n\t\t\t\t}\n\t\t\t\treq[\"messages\"] = combined\n\t\t\t\tneedsReserialization = true\n\t\t\t\t// Strip embedded CC breakpoints from frozen stubs — they waste cache slots.\n\t\t\t\t// InjectFrozenStubCacheBreakpoint adds exactly one below.\n\t\t\t\tif n := StripMessagesCacheControl(req, 0, len(frozen.Messages)); n > 0 {\n\t\t\t\t\ts.logger.Printf(\"[req %d %s tid=%s] SAWTOOTH: stripped %d embedded breakpoints from frozen stubs\", reqIdx, proj, threadID, n)\n\t\t\t\t}\n\t\t\t\ts.logger.Printf(\"%s[req %d %s tid=%s] SAWTOOTH: frozen prefix (%d stubs, %dk msg) + %d fresh (%dk msg) + %dk overhead = %dk total%s\",\n\t\t\t\t\tcolorGreen, reqIdx, proj, threadID, len(frozen.Messages), frozen.Tokens/1000, len(freshMessages), freshTokens/1000, overhead/1000, combinedTokens/1000, colorReset)\n\t\t\t\trawMsgTokens := s.countMessageTokens(messages)\n\t\t\t\ts.setRawEstimate(threadID, rawMsgTokens+overhead)\n\t\t\t\ts.logger.Printf(\"[req %d %s tid=%s] RAW ESTIMATE: msgTokens=%dk + overhead=%dk = %dk (messages=%d)\", reqIdx, proj, threadID, rawMsgTokens/1000, overhead/1000, (rawMsgTokens+overhead)/1000, len(messages))\n\t\t\t\tif InjectFrozenStubCacheBreakpoint(req, len(frozen.Messages)) {\n\t\t\t\t\ts.logger.Printf(\"[req %d %s tid=%s] SAWTOOTH: frozen stub cache breakpoint at messages[%d]\",\n\t\t\t\t\t\treqIdx, proj, threadID, len(frozen.Messages)-1)\n\t\t\t\t}\n\n\t\t\t\t// Ensure frozen stubs are persisted (may have failed on first request after deploy)\n\t\t\t\ts.ensureFrozenPersisted(threadID)\n\t\t\t}\n\t\t}\n\t\tif frozen == nil {\n\t\t\t// No frozen stubs — check trigger\n\t\t\ttriggerReason := s.sawtoothTrigger.ShouldTrigger(threadID, totalTokens)\n\t\t\tif triggerReason != TriggerNone {\n\t\t\t\ts.logger.Printf(\"[req %d %s tid=%s] SAWTOOTH stub-cycle triggered: %s (tokens=%dk)\",\n\t\t\t\t\treqIdx, proj, threadID, triggerReason, totalTokens/1000)\n\n\t\t\t\t// Refresh briefing during stub-cycle — disabled (briefing now via SessionStart hook)\n\t\t\t\t// s.refreshBriefing(proj)\n\t\t\t\t// if s.briefingText != \"\" {\n\t\t\t\t// \tUpsertSystemBlockCached(req, \"yesmem-briefing\", s.briefingText)\n\t\t\t\t// }\n\n\t\t\t\t// Run full stub pipeline\n\t\t\t\ts.setRawEstimate(threadID, totalTokens)\n\t\t\t\t_ = s.runStubCycle(messages, req, reqIdx, proj, threadID, overhead, totalTokens, userQuery, isRetryReq)\n\n\t\t\t\t// Freeze the result\n\t\t\t\tfinalMessages, _ := req[\"messages\"].([]any)\n\t\t\t\tif finalMessages != nil && len(finalMessages) > 0 {\n\t\t\t\t\t// Boundary = last message of original that maps to last stub\n\t\t\t\t\tcutoff := len(messages)\n\t\t\t\t\tsawtoothCutoff = cutoff\n\t\t\t\t\tvar boundaryMsg any\n\t\t\t\t\tif cutoff > 0 {\n\t\t\t\t\t\tboundaryMsg = messages[cutoff-1]\n\t\t\t\t\t}\n\t\t\t\t\tfrozenTokens := s.countMessageTokens(finalMessages)\n\t\t\t\t\tfrozenCount, stripped, breakpointInjected := s.freezeStubsAndInjectBreakpoint(req, threadID, cutoff, boundaryMsg, frozenTokens, totalTokens)\n\t\t\t\t\ts.decay.Persist(threadID)\n\t\t\t\t\tif stripped > 0 {\n\t\t\t\t\t\ts.logger.Printf(\"[req %d %s tid=%s] SAWTOOTH: stripped %d embedded breakpoints before freeze\",\n\t\t\t\t\t\t\treqIdx, proj, threadID, stripped)\n\t\t\t\t\t}\n\t\t\t\t\ts.logger.Printf(\"[req %d %s tid=%s] SAWTOOTH: frozen %d messages at cutoff=%d (~%dk tokens)\",\n\t\t\t\t\t\treqIdx, proj, threadID, frozenCount, cutoff, frozenTokens/1000)\n\t\t\t\t\tif breakpointInjected {\n\t\t\t\t\t\ts.logger.Printf(\"[req %d %s tid=%s] SAWTOOTH: frozen stub cache breakpoint at messages[%d]\",\n\t\t\t\t\t\t\treqIdx, proj, threadID, frozenCount-1)\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tneedsReserialization = true\n\t\t\t} else {\n\t\t\t\t// No trigger, no frozen stubs — eager-stub to delay first collapse\n\t\t\t\tbeforeEager := s.countMessageTokens(messages)\n\t\t\t\tvar stubSticky, stubFresh int\n\t\t\t\tmessages = EagerStubToolResults(messages, 0, s.countTokens, WithStubMemory(s.eagerStubMemory, threadID), WithStubCounters(&stubSticky, &stubFresh))\n\t\t\t\tafterEager := s.countMessageTokens(messages)\n\t\t\t\tif beforeEager != afterEager {\n\t\t\t\t\ts.logger.Printf(\"[req %d %s tid=%s] EAGER-STUB: %dk → %dk (saved %dk) [sticky=%d fresh=%d]\", reqIdx, proj, threadID, beforeEager/1000, afterEager/1000, (beforeEager-afterEager)/1000, stubSticky, stubFresh)\n\t\t\t\t\treq[\"messages\"] = messages\n\t\t\t\t\tneedsReserialization = true\n\t\t\t\t}\n\t\t\t\ts.logger.Printf(\"%s[req %d %s tid=%s] %d messages, ~%dk tokens (sawtooth: no trigger)%s\",\n\t\t\t\t\tcolorDim, reqIdx, proj, threadID, len(messages), totalTokens/1000, colorReset)\n\t\t\t}\n\t\t}\n\t} else {\n\t\t// Legacy path: progressive decay\n\t\tif s.shouldStub(totalTokens, model) {\n\t\t\ts.setRawEstimate(threadID, totalTokens)\n\t\t\t_ = s.runStubCycle(messages, req, reqIdx, proj, threadID, overhead, totalTokens, userQuery, isRetryReq)\n\t\t\tneedsReserialization = true\n\t\t}\n\t}\n\n\t// Persistent timestamp injection: re-injects stored timestamps on ALL previous user messages.\n\t// msg:N is computed from position (counting user messages in raw array), NOT from a counter.\n\t// The injected data is deterministic (same bytes every turn) = cache-safe after initial miss.\n\tif threadID != \"\" {\n\t\tif currentMsgs, ok := req[\"messages\"].([]any); ok && len(currentMsgs) > 0 {\n\t\t\ts.timestampStore.Load(threadID)\n\n\t\t\t// Offset = cutoff - stubsCount: maps combined array index to raw position.\n\t\t\t// Raw position of message at combined index I = cutoff + (I - stubs).\n\t\t\t// msgN = rawPos + 1 = cutoff + (I - stubs) + 1 = (cutoff - stubs) + I + 1.\n\t\t\tmsgOffset := sawtoothCutoff - sawtoothStubs\n\n\t\t\ts.logger.Printf(\"[req %d %s] timestamps: rawTotal=%d, postSawtooth=%d, cutoff=%d, stubs=%d, offset=%d\", reqIdx, proj, rawMsgCount, len(currentMsgs), sawtoothCutoff, sawtoothStubs, msgOffset)\n\n\t\t\t// Pre-store assistant timestamp BEFORE inject loop so it's available immediately\n\t\t\ts.responseTsMu.RLock()\n\t\t\trespTime, hasRespTime := s.responseTimes[threadID]\n\t\t\ts.responseTsMu.RUnlock()\n\t\t\tif hasRespTime {\n\t\t\t\trespStr := shortWeekday(respTime.Weekday()) + \" \" + respTime.Format(\"2006-01-02 15:04:05\")\n\t\t\t\tfor j := len(currentMsgs) - 2; j >= 0; j-- {\n\t\t\t\t\tprevMsg, _ := currentMsgs[j].(map[string]any)\n\t\t\t\t\tif prevMsg == nil || prevMsg[\"role\"] != \"assistant\" {\n\t\t\t\t\t\tcontinue\n\t\t\t\t\t}\n\t\t\t\t\tif !msgHasTextBlock(prevMsg) {\n\t\t\t\t\t\ts.logger.Printf(\"[req %d %s] timestamps: skip assistant[%d] no text block\", reqIdx, proj, j)\n\t\t\t\t\t\tcontinue // skip tool-use-only assistant messages\n\t\t\t\t\t}\n\t\t\t\t\tprevMsgN := msgOffset + j + 1\n\t\t\t\t\tif _, exists := s.timestampStore.Get(threadID, prevMsgN); !exists {\n\t\t\t\t\t\ts.timestampStore.Store(threadID, prevMsgN, &TimestampMeta{Timestamp: respStr})\n\t\t\t\t\t\ts.logger.Printf(\"[req %d %s] timestamps: stored assistant msg:%d ts=%s\", reqIdx, proj, prevMsgN, respStr)\n\t\t\t\t\t} else {\n\t\t\t\t\t\ts.logger.Printf(\"[req %d %s] timestamps: assistant msg:%d already has ts\", reqIdx, proj, prevMsgN)\n\t\t\t\t\t}\n\t\t\t\t\tbreak\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\ts.logger.Printf(\"[req %d %s] timestamps: no responseTimes for thread\", reqIdx, proj)\n\t\t\t}\n\n\t\t\t// Inject [msg:N] (+ timestamp/delta if stored) on all PREVIOUS messages\n\t\t\tif n := InjectTimestamps(s.timestampStore, threadID, currentMsgs, len(currentMsgs)-1, msgOffset, sawtoothStubs); n > 0 {\n\t\t\t\tneedsReserialization = true\n\t\t\t\ts.logger.Printf(\"[req %d %s] timestamps: injected %d\", reqIdx, proj, n)\n\t\t\t}\n\n\t\t\t// Current (last) user message: full timestamp + delta + msg:N from raw count\n\t\t\tlastMsg, _ := currentMsgs[len(currentMsgs)-1].(map[string]any)\n\t\t\tif lastMsg != nil && lastMsg[\"role\"] == \"user\" && !msgHasMetaPrefix(lastMsg) {\n\t\t\t\tnow := time.Now()\n\t\t\t\tnowStr := shortWeekday(now.Weekday()) + \" \" + now.Format(\"2006-01-02 15:04:05\")\n\t\t\t\tentry := &TimestampMeta{Timestamp: nowStr}\n\t\t\t\tif hasRespTime {\n\t\t\t\t\tentry.Delta = formatDelta(now.Sub(respTime))\n\t\t\t\t}\n\t\t\t\ts.timestampStore.Store(threadID, rawMsgCount, entry)\n\t\t\t\tprependMeta(lastMsg, BuildMeta(rawMsgCount, entry))\n\t\t\t\tneedsReserialization = true\n\t\t\t}\n\n\t\t\ts.timestampStore.Persist(threadID)\n\t\t}\n\t}\n\n\t// Associative context injection (for ALL requests, not just stubbed ones)\n\t// Re-enabled with higher thresholds (55/75, max 1) after disabling Fresh Remember.\n\tassocContext := s.findAssociativeContextFor(userQuery, proj, threadID, messages)\n\tif assocContext != \"\" {\n\t\tcurrentMessages, _ := req[\"messages\"].([]any)\n\t\tif currentMessages == nil {\n\t\t\tcurrentMessages = messages\n\t\t}\n\t\treq[\"messages\"] = injectAssociativeContext(currentMessages, assocContext, s.cfg.SawtoothEnabled)\n\t\tneedsReserialization = true\n\t\ts.logger.Printf(\"%s[req %d %s tid=%s] associative context injected%s\", colorBlue, reqIdx, proj, threadID, colorReset)\n\t}\n\n\t// Doc-chunk context injection: separate search path for indexed docs (not skills)\n\tdocContext := s.findDocContextFor(userQuery, proj, messages)\n\tif docContext != \"\" {\n\t\tcurrentMessages, _ := req[\"messages\"].([]any)\n\t\tif currentMessages == nil {\n\t\t\tcurrentMessages = messages\n\t\t}\n\t\treq[\"messages\"] = injectAssociativeContext(currentMessages, docContext, s.cfg.SawtoothEnabled)\n\t\tneedsReserialization = true\n\t\ts.logger.Printf(\"%s[req %d %s tid=%s] doc context injected%s\", colorBlue, reqIdx, proj, threadID, colorReset)\n\t}\n\n\t// Fresh Remember Injection: DISABLED\n\t// Causes echo-loop: Claude saves via remember(), sees it again next turn as\n\t// [yesmem fresh memory], reacts to own output instead of user's request.\n\t// Recovery after /clear is handled by recovery.go (briefing), not here.\n\t// Subagents get their own briefing. No remaining use-case.\n\t// remItems := s.popRecentRemember()\n\tvar remItems []recentLearningItem\n\n\t// Rules re-injection: inject condensed CLAUDE.md rules every ~40k output tokens\n\tif rulesBlock := s.rulesInject(threadID, totalTokens, proj); rulesBlock != \"\" {\n\t\tcurrentMessages, _ := req[\"messages\"].([]any)\n\t\tif currentMessages == nil {\n\t\t\tcurrentMessages = messages\n\t\t}\n\t\treq[\"messages\"] = injectAssociativeContext(currentMessages, s.formatRulesReminder(rulesBlock, proj), s.cfg.SawtoothEnabled)\n\t\tneedsReserialization = true\n\t\ts.logger.Printf(\"%s[req %d %s tid=%s] rules reminder injected%s\", colorBlue, reqIdx, proj, threadID, colorReset)\n\t}\n\n\t// Plan auto-activation: nudge Claude to call set_plan() when reading a plan file\n\tif planFile := detectPlanFileRead(messages); planFile != \"\" {\n\t\tif activePlan, _ := s.getActivePlan(threadID); shouldNudgePlan(planFile, activePlan) {\n\t\t\tAppendSystemBlock(req, \"plan-nudge\",\n\t\t\t\tfmt.Sprintf(\"Du hast einen Implementierungsplan gelesen (%s). Aktiviere ihn jetzt via set_plan() damit Plan-Checkpoints funktionieren und dein Fortschritt getrackt wird.\", filepath.Base(planFile)))\n\t\t\tneedsReserialization = true\n\t\t\ts.logger.Printf(\"%s[req %d %s tid=%s] plan nudge injected for %s%s\", colorBlue, reqIdx, proj, threadID, filepath.Base(planFile), colorReset)\n\t\t}\n\t}\n\n\t// Plan checkpoint: inject plan reminder every ~20k tokens if active plan exists\n\ts.detectPlanToolCall(messages, threadID, totalTokens)\n\tif checkpoint := s.planCheckpointInject(threadID, totalTokens); checkpoint != \"\" {\n\t\tcurrentMessages, _ := req[\"messages\"].([]any)\n\t\tif currentMessages == nil {\n\t\t\tcurrentMessages = messages\n\t\t}\n\t\treq[\"messages\"] = injectAssociativeContext(currentMessages, checkpoint, s.cfg.SawtoothEnabled)\n\t\tneedsReserialization = true\n\t\ts.logger.Printf(\"%s[req %d %s tid=%s] plan checkpoint injected%s\", colorBlue, reqIdx, proj, threadID, colorReset)\n\t}\n\n\t// Docs hint injection: inject docs-available reminder every ~10k tokens (independent of plan)\n\tif docsHint := s.docsHintInject(threadID, totalTokens, proj); docsHint != \"\" {\n\t\tcurrentMessages, _ := req[\"messages\"].([]any)\n\t\tif currentMessages == nil {\n\t\t\tcurrentMessages = messages\n\t\t}\n\t\treq[\"messages\"] = injectAssociativeContext(currentMessages, docsHint, s.cfg.SawtoothEnabled)\n\t\tneedsReserialization = true\n\t\ts.logger.Printf(\"%s[req %d %s tid=%s] docs hint injected%s\", colorBlue, reqIdx, proj, threadID, colorReset)\n\t}\n\tif len(remItems) > 0 {\n\t\tvar lines []string\n\t\tfor _, item := range remItems {\n\t\t\tif item.ID > 0 {\n\t\t\t\tlines = append(lines, fmt.Sprintf(\"- [ID:%d] %s\", item.ID, truncateStr(item.Text, 200)))\n\t\t\t} else {\n\t\t\t\tlines = append(lines, \"- \"+truncateStr(item.Text, 200))\n\t\t\t}\n\t\t}\n\t\tremContext := fmt.Sprintf(\"[yesmem fresh memory]\\nGerade gelernt:\\n%s\\n%s\\n[/yesmem fresh memory]\", strings.Join(lines, \"\\n\"), InlineReflectionHint)\n\t\tcurrentMessages, _ := req[\"messages\"].([]any)\n\t\tif currentMessages == nil {\n\t\t\tcurrentMessages = messages\n\t\t}\n\t\treq[\"messages\"] = injectAssociativeContext(currentMessages, remContext, s.cfg.SawtoothEnabled)\n\t\tneedsReserialization = true\n\t\ts.logger.Printf(\"%s[req %d %s tid=%s] fresh remember injected (%d items)%s\", colorBlue, reqIdx, proj, threadID, len(remItems), colorReset)\n\t}\n\n\t// Skill eval injection: mandatory evaluation instruction for real user input only\n\tif threadID != \"\" && proj != \"\" && isUserInputTurn(messages) {\n\t\t// First: detect any skill activations from message history (updates tracker)\n\t\ts.syncSkillActivations(messages, proj, threadID)\n\n\t\t// Inject mandatory skill evaluation block into last user message\n\t\tskillEval := buildSkillEvalBlock(s.cfg.SkillEvalInject)\n\t\tcurrentMessages, _ := req[\"messages\"].([]any)\n\t\tif currentMessages == nil {\n\t\t\tcurrentMessages = messages\n\t\t}\n\t\tif skillEval != \"\" {\n\t\t\treq[\"messages\"] = injectAssociativeContext(currentMessages, skillEval, s.cfg.SawtoothEnabled)\n\t\t\tneedsReserialization = true\n\t\t\ts.logger.Printf(\"%s[req %d %s tid=%s] skill-eval injected (mode=%s)%s\", colorBlue, reqIdx, proj, threadID, s.cfg.SkillEvalInject, colorReset)\n\t\t}\n\t}\n\n\t// === Collect injected learning IDs for next turn's inline reflection ===\n\tif threadID != \"\" {\n\t\tcurrentIDs := make(map[int64]string) // id → source\n\t\t// Source 1: Briefing learnings (cached, same every turn)\n\t\tbriefingSnapshot, _, _ := s.getCachedBriefing(threadID, proj)\n\t\tif briefingSnapshot != \"\" {\n\t\t\tfor _, m := range idPattern.FindAllStringSubmatch(briefingSnapshot, -1) {\n\t\t\t\tif id, err := strconv.ParseInt(m[1], 10, 64); err == nil {\n\t\t\t\t\tcurrentIDs[id] = \"briefing\"\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\t// Source 2: Associative context (contextual, per-turn)\n\t\tif assocContext != \"\" {\n\t\t\tfor _, m := range idPattern.FindAllStringSubmatch(assocContext, -1) {\n\t\t\t\tif id, err := strconv.ParseInt(m[1], 10, 64); err == nil {\n\t\t\t\t\tcurrentIDs[id] = \"associative\" // overwrites briefing if both — associative is the stronger signal\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\t// Source 3: Fresh remember items (just learned)\n\t\tfor _, item := range remItems {\n\t\t\tif item.ID > 0 {\n\t\t\t\tcurrentIDs[item.ID] = \"fresh\"\n\t\t\t}\n\t\t}\n\t\ts.lastInjectedIDsMu.Lock()\n\t\tif len(currentIDs) > 0 {\n\t\t\t// Accumulate IDs across turns — don't overwrite.\n\t\t\t// Claude may reference an ID from turn 30 in turn 50.\n\t\t\texisting := s.lastInjectedIDs[threadID]\n\t\t\tif existing == nil {\n\t\t\t\texisting = make(map[int64]string)\n\t\t\t}\n\t\t\tfor id, source := range currentIDs {\n\t\t\t\texisting[id] = source\n\t\t\t}\n\t\t\ts.lastInjectedIDs[threadID] = existing\n\t\t}\n\t\ts.lastInjectedIDsMu.Unlock()\n\t}\n\n\t// Think reminder injection — only for real user turns, not tool continuations\n\tif threadID != \"\" {\n\t\tcurrentMessages, _ := req[\"messages\"].([]any)\n\t\tif currentMessages == nil {\n\t\t\tcurrentMessages = messages\n\t\t}\n\t\tif lastUserHasText(currentMessages) {\n\t\t\tthinkReminder := s.buildThinkReminder(threadID, threadID, false)\n\t\t\tif thinkReminder != \"\" {\n\t\t\t\treq[\"messages\"] = injectAssociativeContext(currentMessages, thinkReminder, s.cfg.SawtoothEnabled)\n\t\t\t\tneedsReserialization = true\n\t\t\t}\n\t\t}\n\n\t\t// Refresh frozen-prefix snapshot with the post-timestamps bytes that\n\t\t// will appear in the wire body for the frozen range. This MUST run\n\t\t// BEFORE injectBriefingTurn / injectCodeMapTurn / injectCapabilitiesTurn:\n\t\t// those stages prepend or insert turns into req[\"messages\"], which\n\t\t// shifts the frozen range to a higher offset. Slicing\n\t\t// req[\"messages\"][:frozenLen] AFTER inject would capture the injected\n\t\t// turns at the head and corrupt the stored frozen prefix, breaking\n\t\t// cache continuity for 5-9 turns after every emergency-sawtooth.\n\t\tif frozenLen := s.frozenStubs.LengthFor(threadID); frozenLen > 0 {\n\t\t\tif msgs, ok := req[\"messages\"].([]any); ok && len(msgs) >= frozenLen {\n\t\t\t\tif s.frozenStubs.UpdateMessages(threadID, msgs[:frozenLen]) {\n\t\t\t\t\ts.logger.Printf(\"[req %d %s tid=%s] FROZEN-SNAPSHOT: refreshed %d pre-inject messages\", reqIdx, proj, threadID, frozenLen)\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\t// Briefing injection: prepend user/assistant turn pair at beginning of messages.\n\t\t// Static per session → stable prefix → cacheable. Must be before dialog injection.\n\t\tif s.injectBriefingTurn(req, reqIdx, proj, threadID) {\n\t\t\tneedsReserialization = true\n\t\t}\n\n\t\t// Code Map injection: insert after briefing turn pair.\n\t\tif s.injectCodeMapTurn(req, reqIdx, proj, threadID) {\n\t\t\tneedsReserialization = true\n\t\t}\n\n\t\t// Active capabilities injection: insert user/assistant pair directly\n\t\t// before the last user message. Fresh per turn (no cache) so\n\t\t// activate/deactivate takes effect immediately. Must run AFTER\n\t\t// briefing/codeMap (both prepend at beginning) and BEFORE dialog\n\t\t// injection (which appends at end) to preserve cache for the\n\t\t// briefing prefix and keep the injection close to the real user\n\t\t// turn.\n\t\tif s.injectCapabilitiesTurn(req, threadID) {\n\t\t\tneedsReserialization = true\n\t\t}\n\n\t\t// Dialog injection: append at end of messages with pseudo-padding for alternation.\n\t\t// Always works regardless of tool_result chains or conversation state.\n\t\tdr := s.checkDialogMessages(threadID, proj)\n\t\tif dr.Extra != \"\" {\n\t\t\t// Prepend session ID for agent-to-agent communication\n\t\t\tdr.Extra = fmt.Sprintf(\"DEINE_SESSION_ID: %s\\n\", threadID) + dr.Extra\n\t\t\tinjected := false\n\t\t\tif msgs, ok := req[\"messages\"].([]any); ok && len(msgs) > 0 {\n\t\t\t\t// Pad with empty assistant if last message is user (maintains alternation)\n\t\t\t\tlastMsg, _ := msgs[len(msgs)-1].(map[string]any)\n\t\t\t\tif lastMsg != nil && lastMsg[\"role\"] == \"user\" {\n\t\t\t\t\tmsgs = append(msgs, map[string]any{\n\t\t\t\t\t\t\"role\": \"assistant\",\n\t\t\t\t\t\t\"content\": \"​\", // zero-width space — invisible padding\n\t\t\t\t\t})\n\t\t\t\t}\n\t\t\t\t// Append dialog as new user message (becomes the last message Claude sees)\n\t\t\t\tmsgs = append(msgs, map[string]any{\n\t\t\t\t\t\"role\": \"user\",\n\t\t\t\t\t\"content\": \"\\n\" + dr.Extra + \"\\n\",\n\t\t\t\t})\n\t\t\t\treq[\"messages\"] = msgs\n\t\t\t\tneedsReserialization = true\n\t\t\t\tinjected = true\n\t\t\t}\n\t\t\t// Mark as read after 3 injection turns — long enough for Claude to process,\n\t\t\t// resilient against fast ping-pong sessions (time-based was broken for <30s turns).\n\t\t\tif injected && dr.HasUnread && dr.SessionID != \"\" {\n\t\t\t\tif s.shouldMarkChannelRead(dr) {\n\t\t\t\t\ts.markDialogRead(dr)\n\t\t\t\t}\n\t\t\t}\n\t\t\ts.logger.Printf(\"[dialog] injection injected=%v extra_len=%d\", injected, len(dr.Extra))\n\t\t}\n\t}\n\n\t// Signal tools are NOT injected into the main request.\n\t// They are collected via a separate async reflection call after the response.\n\t// See fireReflectionCall() in reflection.go.\n\t// NEVER put _signal_* tools back here — the model ignores them among 44+ real tools.\n\n\t// Legacy assistant annotation: timestamp on last assistant message after cache breakpoint.\n\t// User timestamps are now handled by TimestampStore (injected earlier in the pipeline).\n\tif threadID != \"\" {\n\t\tif msgs, ok := req[\"messages\"].([]any); ok && len(msgs) > 0 {\n\t\t\t// Find last cache_control breakpoint in messages\n\t\t\tcacheIdx := -1\n\t\t\tfor i, m := range msgs {\n\t\t\t\tmsg, _ := m.(map[string]any)\n\t\t\t\tif msg == nil {\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\t\t\t\tif _, has := msg[\"cache_control\"]; has {\n\t\t\t\t\tcacheIdx = i\n\t\t\t\t}\n\t\t\t\tif content, ok := msg[\"content\"].([]any); ok {\n\t\t\t\t\tfor _, block := range content {\n\t\t\t\t\t\tif b, ok := block.(map[string]any); ok {\n\t\t\t\t\t\t\tif _, has := b[\"cache_control\"]; has {\n\t\t\t\t\t\t\t\tcacheIdx = i\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t\tstartIdx := cacheIdx + 1\n\t\t\tif startIdx < 0 {\n\t\t\t\tstartIdx = 0\n\t\t\t}\n\t\t\ts.responseTsMu.RLock()\n\t\t\trespTime, hasRespTime := s.responseTimes[threadID]\n\t\t\ts.responseTsMu.RUnlock()\n\t\t\tif hasRespTime {\n\t\t\t\tfor i := len(msgs) - 1; i >= startIdx; i-- {\n\t\t\t\t\tmsg, _ := msgs[i].(map[string]any)\n\t\t\t\t\tif msg != nil && msg[\"role\"] == \"assistant\" {\n\t\t\t\t\t\tmeta := fmt.Sprintf(\"[%s] [msg:%d]\", respTime.Format(\"2006-01-02 15:04:05\"), i)\n\t\t\t\t\t\tprependMeta(msg, meta)\n\t\t\t\t\t\tneedsReserialization = true\n\t\t\t\t\t\tbreak\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\t// Self-Priming: DISABLED (experimental, see signal_handlers.go comment block).\n\t// The reflection-based selfprime (Haiku) was too inaccurate to be useful —\n\t// the main model adapts to user context organically from message history.\n\t// Kept for reference; re-enable by uncommenting below + setting ShouldActivate=true.\n\t//\n\t// s.selfPrimeMu.RLock()\n\t// anchor := s.selfPrimes[threadID]\n\t// s.selfPrimeMu.RUnlock()\n\t// if anchor != \"\" {\n\t// \tif msgs, ok := req[\"messages\"].([]any); ok && len(msgs) > 0 {\n\t// \t\tlastMsg, _ := msgs[len(msgs)-1].(map[string]any)\n\t// \t\tif lastMsg != nil && lastMsg[\"role\"] == \"user\" {\n\t// \t\t\tprimeBlock := map[string]any{\n\t// \t\t\t\t\"type\": \"text\",\n\t// \t\t\t\t\"text\": \"[self-prime]\\n[Self-Prime von deiner letzten Antwort]: \" + anchor,\n\t// \t\t\t}\n\t// \t\t\tif content, ok := lastMsg[\"content\"].([]any); ok {\n\t// \t\t\t\tlastMsg[\"content\"] = append(content, primeBlock)\n\t// \t\t\t} else if text, ok := lastMsg[\"content\"].(string); ok {\n\t// \t\t\t\tlastMsg[\"content\"] = []any{\n\t// \t\t\t\t\tmap[string]any{\"type\": \"text\", \"text\": text},\n\t// \t\t\t\t\tprimeBlock,\n\t// \t\t\t\t}\n\t// \t\t\t}\n\t// \t\t\tneedsReserialization = true\n\t// \t\t}\n\t// \t}\n\t// }\n\n\t// Prompt Caching: inject cache_control breakpoints on system + tools\n\t// Sawtooth: disabled (all 4 slots are used by CC system (2) + briefing (1) + CC messages (1))\n\t// Legacy: count existing breakpoints and fill up remaining slots.\n\tif !s.cfg.SawtoothEnabled && s.cacheGate.ShouldCache() {\n\t\tif n := InjectCacheBreakpoints(req, s.logger); n > 0 {\n\t\t\tneedsReserialization = true\n\t\t\ts.logger.Printf(\"[req %d %s] prompt cache: %d breakpoints injected\", reqIdx, proj, n)\n\t\t} else {\n\t\t\ts.logger.Printf(\"[req %d %s] prompt cache: no budget (4/4 already set)\", reqIdx, proj)\n\t\t}\n\t}\n\n\t// Final: upgrade cache TTL + enforce breakpoint limit (must run AFTER all injections + InjectCacheBreakpoints)\n\t// Shift the last messages-breakpoint from unstable user message to stable assistant message.\n\t// Text user messages change between turns (timestamps, reminders, structure); assistant messages don't.\n\t// Tool-result messages are excluded — they are stable cache anchors.\n\tif ShiftMessageBreakpoint(req) {\n\t\tneedsReserialization = true\n\t}\n\t// TTL upgrade only for API key auth — subscription (OAuth) users should not get extended TTL.\n\tif s.cfg.CacheTTL != \"\" && s.cfg.CacheTTL != \"ephemeral\" && IsAPIKeyAuth(r.Header) {\n\t\tif n := UpgradeCacheTTL(req, s.cfg.CacheTTL); n > 0 {\n\t\t\tneedsReserialization = true\n\t\t\ts.logger.Printf(\"[req %d %s] cache TTL upgraded: %d blocks → %s\", reqIdx, proj, n, s.cfg.CacheTTL)\n\t\t}\n\t}\n\tif n := EnforceCacheBreakpointLimit(req, maxCacheBreakpoints); n > 0 {\n\t\tneedsReserialization = true\n\t\ts.logger.Printf(\"[req %d %s] prompt cache: trimmed %d surplus breakpoints\", reqIdx, proj, n)\n\t}\n\n\t// Pre-flight: validate tool_use/tool_result pairing before forwarding.\n\t// Repairs orphans created by Stubify/Collapse/Injection pipeline.\n\tif msgs, ok := req[\"messages\"].([]any); ok {\n\t\trepaired, orphans := validateToolPairs(msgs, s.logger)\n\t\tif orphans > 0 {\n\t\t\treq[\"messages\"] = repaired\n\t\t\ts.logger.Printf(\"%s[req %d %s tid=%s] validate: repaired %d orphan tool_result(s)%s\", colorOrange, reqIdx, proj, threadID, orphans, colorReset)\n\t\t\tneedsReserialization = true\n\t\t}\n\t}\n\n\t// Early-out: skip re-serialization when nothing changed\n\tif !needsReserialization {\n\t\ts.logger.Printf(\"%s[req %d %s tid=%s] %d messages, ~%dk tokens (no stubbing, threshold=%dk)%s\", colorDim, reqIdx, proj, threadID, len(messages), totalTokens/1000, s.effectiveTokenThreshold(model)/1000, colorReset)\n\t\ts.forwardWithAnnotation(w, r, body, reqIdx, toolUseIDs, proj, threadID, msgCount, totalTokens)\n\t\treturn\n\t}\n\n\t// Re-serialize\n\tbody, err = json.Marshal(req)\n\tif err != nil {\n\t\ts.logger.Printf(\"%s[req %d %s tid=%s] re-marshal error: %v%s\", colorRed, reqIdx, proj, threadID, err, colorReset)\n\t\thttp.Error(w, \"internal proxy error\", http.StatusInternalServerError)\n\t\treturn\n\t}\n\n\t// Debug: dump final request body to file (enable with YESMEM_PROXY_DEBUG=1)\n\tif needsReserialization && os.Getenv(\"YESMEM_PROXY_DEBUG\") == \"1\" {\n\t\tdumpDir := filepath.Join(s.cfg.DataDir, \"debug\")\n\t\tos.MkdirAll(dumpDir, 0755)\n\t\tts := time.Now().Format(\"20060102-150405\")\n\t\tdumpPath := fmt.Sprintf(\"%s/req_%s_%d.json\", dumpDir, ts, reqIdx)\n\t\tos.WriteFile(dumpPath, body, 0644)\n\t\ts.logger.Printf(\"[req %d] dumped %dk to %s\", reqIdx, len(body)/1024, dumpPath)\n\t}\n\n\t// Task #7: Mark this request fingerprint for retry detection\n\tif !isRetryReq {\n\t\ts.markRequest(fp)\n\t}\n\n\t// Forward to Anthropic with SSE annotation extraction\n\t// Use original msgCount (pre-injection) so actual-based estimation stays consistent across turns\n\t// Use actual-based totalTokens as estimate when available (more accurate than full recount)\n\tfinalEstimate := totalTokens\n\tif s.sawtoothTrigger == nil || s.sawtoothTrigger.GetLastTokens(threadID) == 0 {\n\t\t// First request: no actual yet, do a full recount of post-injection messages\n\t\tfinalMsgs, _ := req[\"messages\"].([]any)\n\t\tfinalEstimate = s.measureOverhead(req) + s.countMessageTokens(finalMsgs)\n\t}\n\ts.logger.Printf(\"[req %d %s tid=%s] FINAL: ~%dk estimated (after all injections)\", reqIdx, proj, threadID, finalEstimate/1000)\n\tif raw := s.getRawEstimate(threadID); raw > 0 {\n\t\ts.cacheStatusWriter.UpdateRawForThread(threadID, raw)\n\t}\n\ts.forwardWithAnnotation(w, r, body, reqIdx, toolUseIDs, proj, threadID, msgCount, finalEstimate)\n}\n\n// bytesPerToken is the approximation factor for token estimation.\n// Anthropic recommends ~3.5 chars/token; we use 3.6 for slight overestimation\n// (triggers stubbing a bit earlier = safer for context budget).\nconst bytesPerToken = 3.6\n\n// TokenEstimateFunc is the central token estimation function.\n// All stubbing/budget decisions use this. Receives extracted text content\n// (not JSON wire format) so the tokenizer counts actual content tokens.\ntype TokenEstimateFunc func(text string) int\n"}, "ead67e8e42c56b7d6b2c38289f48ef9c70c511eb": {"t": 1785759352.7313278, "n": 60126, "h": "8215e1cbd669ccad", "c": "package proxy\n\nimport (\n\t\"context\"\n\t\"crypto/tls\"\n\t\"encoding/json\"\n\t\"fmt\"\n\t\"io\"\n\t\"log\"\n\t\"net\"\n\t\"net/http\"\n\t\"os\"\n\t\"os/signal\"\n\t\"path/filepath\"\n\t\"strconv\"\n\t\"strings\"\n\t\"sync\"\n\t\"sync/atomic\"\n\t\"syscall\"\n\t\"time\"\n\n\ttokenizer \"github.com/qhenkart/anthropic-tokenizer-go\"\n)\n\n// Config holds proxy configuration.\ntype Config struct {\n\tListenAddr string // e.g. \":9099\"\n\tTargetURL string // e.g. \"https://api.anthropic.com\"\n\tTokenThreshold int // trigger stubbing above this estimated token count\n\tTokenMinimumThreshold int // stub down to this floor\n\tTokenThresholds map[string]int // model-specific thresholds: {\"opus\": 180000, \"haiku\": 130000}\n\tKeepRecent int // number of messages to always keep unmodified\n\tDataDir string // yesmem data directory for DB access\n\tOpenAITargetURL string // upstream for OpenAI-format clients; if empty, uses TargetURL\n\n\t// Signal reflection\n\tSignalsEnabled bool // enable async signal reflection calls\n\tSignalsMode string // \"reflection\" = separate API call\n\tSignalsEveryNTurns int // every N end_turn responses\n\tSignalsModel string // full model ID, e.g. \"claude-sonnet-4-6\"\n\tAPIKey string // needed for reflection API calls\n\n\t// Sawtooth cache optimization\n\tSawtoothEnabled bool // use sawtooth instead of progressive decay (default: true)\n\tCacheTTL string // \"ephemeral\" (5m) or \"1h\" (extended)\n\n\t// Usage deflation: scale down input_tokens reported to CC to suppress \"Context low\" warning.\n\t// 0 = disabled, 0.7 = report 70% of actual tokens. Real values kept for internal tracking.\n\tUsageDeflationFactor float64\n\n\t// System prompt rewriting\n\tPromptUngate bool // strip \"may or may not be relevant\" disclaimer from CLAUDE.md injection\n\tPromptRewrite bool // strip \"Output efficiency\" + \"short and concise\", inject Ant-quality directives\n\tPromptEnhance bool // CLAUDE.md authority reinforcement, comment discipline, persona-based tone\n\tEffortFloor string // minimum effort level: \"low\", \"medium\", \"high\", \"max\" (empty = off)\n\tSkillEvalInject string // \"true\" = verbose eval, \"silent\" = internal eval only, \"false\" = disabled\n\n\t// Cache keepalive\n\tCacheKeepaliveEnabled bool\n\tCacheKeepaliveMode string // \"auto\", \"5m\", \"1h\"\n\tCacheKeepalivePings5m int\n\tCacheKeepalivePings1h int\n\n\t// Forked agents\n\tForkedAgentsEnabled bool\n\tForkedAgentsModel string // full model ID\n\tForkedAgentsTokenGrowthTrigger int\n\tForkedAgentsMaxFailures int\n\tForkedAgentsMaxForksPerSession int\n\tForkedAgentsDebug bool\n\n\t// Cache state management\n\tResetCache bool // clear persisted frozen stubs and decay state on startup\n}\n\nconst maxAnnotations = 5000 // evict oldest when exceeded\n\n// ANSI color codes for log output\nconst (\n\tcolorReset = \"\\033[0m\"\n\tcolorDim = \"\\033[2m\" // gray — passthrough / no stubbing\n\tcolorGreen = \"\\033[32m\" // stubbed / compressed\n\tcolorLightGreen = \"\\033[92m\" // FINAL without compression\n\tcolorBlue = \"\\033[34m\" // injection\n\tcolorOrange = \"\\033[33m\" // skip / retry\n\tcolorYellow = \"\\033[33m\" // warnings\n\tcolorRed = \"\\033[31m\" // errors\n)\n\n// Server is the infinite-thread proxy.\ntype Server struct {\n\tcfg Config\n\thttpClient *http.Client\n\tlogger *log.Logger\n\trequestIdx atomic.Int64\n\n\t// Post-hoc annotations: tool_use_id → first ~120 chars of Claude's response\n\tmu sync.RWMutex\n\tannotations map[string]string\n\n\t// Progressive decay tracker\n\tdecay *DecayTracker\n\n\t// Living narrative block\n\tnarrative *Narrative\n\n\t// Pivot moments cache (from daemon)\n\tpivotMu sync.RWMutex\n\tpivotTexts []string\n\tpivotCached time.Time\n\n\t// Aggregate stats (Task #2)\n\tstats *ProxyStats\n\n\t// Local tokenizer for accurate token counting\n\ttokenizer *tokenizer.Tokenizer\n\n\t// Cached overhead tokens (system + tools), set on first request per thread\n\toverheadMu sync.RWMutex\n\toverheadTokens int // unused, kept for interface compat\n\n\t// Hysteresis state for stubbing (Task #6)\n\tstubActive atomic.Bool\n\n\t// Daemon connection state — first successful connect flips to true\n\tdaemonReady atomic.Bool\n\n\t// Retry detection (Task #7)\n\tretryMu sync.RWMutex\n\tlastFingerprint string\n\n\t// Session start time for temporal annotation in archive blocks\n\tsessionStartMu sync.RWMutex\n\tsessionStartTime time.Time\n\n\t// Runtime config override (set via MCP set_config tool)\n\tconfigOverrideMu sync.RWMutex\n\ttokenThresholdOverrides map[string]int // model-key → threshold, \"\" = global fallback\n\n\t// Briefing cache — loaded once per thread, injected as user/assistant message pair\n\tbriefingMu sync.RWMutex\n\tbriefingText string\n\tcodeMapText string\n\n\t// Cognitive signal bus — routes _signal_* tool calls to handlers\n\tsignalBus *SignalBus\n\n\t// Inline reflection: track which learning IDs were injected per thread\n\tlastInjectedIDsMu sync.Mutex\n\tlastInjectedIDs map[string]map[int64]string // threadID → id → source (\"briefing\"|\"associative\"|\"fresh\")\n\tsessionInjectCounts map[string]map[int64]int // threadID → learningID → injection count this session\n\tlastTurnInjected map[string]map[int64]bool // threadID → learningIDs injected in previous turn\n\n\t// Self-priming anchors: threadID → last self-prime text\n\tselfPrimeMu sync.RWMutex\n\tselfPrimes map[string]string\n\n\t// Timestamp tracking: threadID → time of last response completion\n\tresponseTsMu sync.RWMutex\n\tresponseTimes map[string]time.Time\n\n\t// Think reminder: per-thread request counter (replaces hook-think file-based counter)\n\tthinkMu sync.Mutex\n\tthinkCounters map[string]int // threadID → request count\n\n\tchannelMu sync.Mutex\n\tchannelInjectCount map[string]int // sessionID → injection turn count\n\n\t// Prompt cache gating — enables cache_control breakpoints when requests are frequent\n\tcacheGate *CacheGate\n\n\t// Sawtooth cache optimization\n\tfrozenStubs *FrozenStubs\n\tsawtoothTrigger *SawtoothTrigger\n\ttimestampStore *TimestampStore\n\tcacheStatusWriter *CacheStatusWriter\n\tcacheKeepalive *CacheKeepalive\n\tcacheTTLDetector *CacheTTLDetector\n\n\t// Cumulative token savings from collapsing (per threadID)\n\trawSavingsMu sync.Mutex\n\trawSavings map[string]int\n\n\t// Skill hint tracking: per-thread active skills\n\tskillTracker *skillHintTracker\n\n\t// Forked agent state: per-thread token growth + failure tracking\n\tforkState *ForkState\n\tforkConfigs []ForkConfig\n\n\t// Rules re-injection: condensed CLAUDE.md rules injected every ~40k tokens\n\trulesMu sync.RWMutex\n\trulesBlock string // cached condensed rules (fetched once from daemon)\n\trulesTokenCount map[string]int // threadID → tokens since last rules injection\n\trulesCollapseInjected map[string]bool // threadID → true if collapse already injected rules (reset by normal inject)\n\tmsgCounters *msgCounters // global per-thread msg counter, persists across collapses\n\n\t// Loop detection: per-thread warning state\n\tloopMu sync.Mutex\n\tloopStates map[string]*LoopState // threadID → state\n}\n\n// Run starts the proxy server and blocks until interrupted.\nfunc Run(cfg Config) error {\n\ts := &Server{\n\t\tcfg: cfg,\n\t\thttpClient: &http.Client{\n\t\t\tTimeout: 10 * time.Minute,\n\t\t\tTransport: &http.Transport{\n\t\t\t\tTLSClientConfig: &tls.Config{},\n\t\t\t\tMaxIdleConns: 10,\n\t\t\t\tIdleConnTimeout: 90 * time.Second,\n\t\t\t\tDisableCompression: true,\n\t\t\t\tResponseHeaderTimeout: 60 * time.Second,\n\t\t\t},\n\t\t},\n\t\tlogger: createLogger(cfg.DataDir),\n\t\tannotations: make(map[string]string),\n\t\tdecay: NewDecayTracker(),\n\t\tnarrative: NewNarrative(),\n\t\tstats: &ProxyStats{startTime: time.Now()},\n\t\tselfPrimes: make(map[string]string),\n\t\tlastInjectedIDs: make(map[string]map[int64]string),\n\t\tsessionInjectCounts: make(map[string]map[int64]int),\n\t\tlastTurnInjected: make(map[string]map[int64]bool),\n\t\tresponseTimes: make(map[string]time.Time),\n\t\tthinkCounters: make(map[string]int),\n\t\tchannelInjectCount: make(map[string]int),\n\t\tcacheGate: NewCacheGate(cacheGapForTTL(cfg.CacheTTL)),\n\t\tfrozenStubs: NewFrozenStubsWithTTL(sawtoothTTLForCacheTTL(cfg.CacheTTL)),\n\t\ttimestampStore: NewTimestampStore(),\n\t\tsawtoothTrigger: NewSawtoothTrigger(cacheGapForTTL(cfg.CacheTTL), cfg.TokenThreshold, cfg.TokenMinimumThreshold),\n\t\tcacheStatusWriter: NewCacheStatusWriter(cfg.DataDir, cfg.CacheTTL, cfg.TokenThreshold, cfg.TokenMinimumThreshold),\n\t\tcacheTTLDetector: NewCacheTTLDetectorWithPersist(cfg.DataDir),\n\t\tskillTracker: newSkillHintTracker(),\n\t\trulesTokenCount: make(map[string]int),\n\t\trulesCollapseInjected: make(map[string]bool),\n\t\tmsgCounters: newMsgCounters(),\n\t\tloopStates: make(map[string]*LoopState),\n\t\tforkState: NewForkState(cfg.ForkedAgentsTokenGrowthTrigger, cfg.ForkedAgentsMaxFailures, cfg.ForkedAgentsMaxForksPerSession),\n\t\tforkConfigs: []ForkConfig{},\n\t}\n\n\t// Log persisted detection state\n\tif sup := s.cacheTTLDetector.Is1hSupported(); sup != nil {\n\t\ts.logger.Printf(\"[proxy] loaded persisted TTL detection: 1h_supported=%v\", *sup)\n\t\tif *sup && s.frozenStubs != nil {\n\t\t\ts.frozenStubs.UpdateTTL(sawtoothTTLForCacheTTL(\"1h\"))\n\t\t}\n\t}\n\n\tif cfg.CacheKeepaliveEnabled {\n\t\ts.cacheKeepalive = NewCacheKeepalive(CacheKeepaliveConfig{\n\t\t\tTarget: cfg.TargetURL,\n\t\t\tMode: cfg.CacheKeepaliveMode,\n\t\t\tPings5m: cfg.CacheKeepalivePings5m,\n\t\t\tPings1h: cfg.CacheKeepalivePings1h,\n\t\t\tDetector: s.cacheTTLDetector,\n\t\t\tLogger: s.logger,\n\t\t\tOnPing: func(threadID string, cacheRead, cacheWrite int) {\n\t\t\t\ts.cacheTTLDetector.RecordResponse(cacheRead, cacheWrite, 0)\n\t\t\t\tif s.frozenStubs != nil {\n\t\t\t\t\ts.frozenStubs.Touch(threadID)\n\t\t\t\t}\n\t\t\t\tif s.sawtoothTrigger != nil {\n\t\t\t\t\ts.sawtoothTrigger.TouchRequestTime(threadID)\n\t\t\t\t}\n\t\t\t},\n\t\t})\n\t}\n\n\t// Wire detector + keepalive to status writer for terminal display\n\ts.cacheStatusWriter.SetDetector(s.cacheTTLDetector)\n\tif s.cacheKeepalive != nil {\n\t\ts.cacheStatusWriter.SetKeepalive(s.cacheKeepalive)\n\t}\n\n\t// Register forked agent configs\n\tif cfg.ForkedAgentsEnabled {\n\t\ts.forkConfigs = append(s.forkConfigs, NewExtractAndEvaluateConfig(cfg.ForkedAgentsModel))\n\t\tmodelDesc := cfg.ForkedAgentsModel\n\t\tif modelDesc == \"\" {\n\t\t\tmodelDesc = \"(same as main thread)\"\n\t\t}\n\t\ts.logger.Printf(\"[proxy] forked agents enabled: %d configs, model=%s, trigger=%dk tokens\",\n\t\t\tlen(s.forkConfigs), modelDesc, cfg.ForkedAgentsTokenGrowthTrigger/1000)\n\t}\n\n\t// Wire sawtooth token persistence via daemon RPC\n\ts.sawtoothTrigger.SetPersistFunc(func(key, value string) {\n\t\tif _, err := s.queryDaemon(\"set_proxy_state\", map[string]any{\"key\": key, \"value\": value}); err != nil {\n\t\t\ts.logger.Printf(\"[sawtooth] persist failed for %s: %v\", key, err)\n\t\t}\n\t})\n\ts.sawtoothTrigger.SetLoadFunc(func(key string) (string, bool) {\n\t\tresult, err := s.queryDaemon(\"get_proxy_state\", map[string]any{\"key\": key})\n\t\tif err != nil || result == nil {\n\t\t\treturn \"\", false\n\t\t}\n\t\tvar resp struct {\n\t\t\tValue string `json:\"value\"`\n\t\t}\n\t\tif json.Unmarshal(result, &resp) != nil || resp.Value == \"\" {\n\t\t\treturn \"\", false\n\t\t}\n\t\treturn resp.Value, true\n\t})\n\n\t// Wire frozen stubs persistence via same daemon RPC\n\ts.frozenStubs.SetPersistFunc(func(key, value string) {\n\t\tif _, err := s.queryDaemon(\"set_proxy_state\", map[string]any{\"key\": key, \"value\": value}); err != nil {\n\t\t\ts.logger.Printf(\"[frozen] persist failed for %s: %v\", key, err)\n\t\t}\n\t})\n\ts.frozenStubs.SetLoadFunc(func(key string) (string, bool) {\n\t\tresult, err := s.queryDaemon(\"get_proxy_state\", map[string]any{\"key\": key})\n\t\tif err != nil || result == nil {\n\t\t\treturn \"\", false\n\t\t}\n\t\tvar resp struct {\n\t\t\tValue string `json:\"value\"`\n\t\t}\n\t\tif json.Unmarshal(result, &resp) != nil || resp.Value == \"\" {\n\t\t\treturn \"\", false\n\t\t}\n\t\treturn resp.Value, true\n\t})\n\n\t// Wire decay tracker persistence via same daemon RPC\n\ts.decay.SetPersistFunc(func(key, value string) {\n\t\tif _, err := s.queryDaemon(\"set_proxy_state\", map[string]any{\"key\": key, \"value\": value}); err != nil {\n\t\t\ts.logger.Printf(\"[decay] persist failed for %s: %v\", key, err)\n\t\t}\n\t})\n\ts.decay.SetLoadFunc(func(key string) (string, bool) {\n\t\tresult, err := s.queryDaemon(\"get_proxy_state\", map[string]any{\"key\": key})\n\t\tif err != nil || result == nil {\n\t\t\treturn \"\", false\n\t\t}\n\t\tvar resp struct {\n\t\t\tValue string `json:\"value\"`\n\t\t}\n\t\tif json.Unmarshal(result, &resp) != nil || resp.Value == \"\" {\n\t\t\treturn \"\", false\n\t\t}\n\t\treturn resp.Value, true\n\t})\n\n\t// Wire timestamp store persistence via same daemon RPC\n\ts.timestampStore.SetPersistFunc(func(key, value string) {\n\t\tif _, err := s.queryDaemon(\"set_proxy_state\", map[string]any{\"key\": key, \"value\": value}); err != nil {\n\t\t\ts.logger.Printf(\"[timestamps] persist failed for %s: %v\", key, err)\n\t\t}\n\t})\n\ts.timestampStore.SetLoadFunc(func(key string) (string, bool) {\n\t\tresult, err := s.queryDaemon(\"get_proxy_state\", map[string]any{\"key\": key})\n\t\tif err != nil || result == nil {\n\t\t\treturn \"\", false\n\t\t}\n\t\tvar resp struct {\n\t\t\tValue string `json:\"value\"`\n\t\t}\n\t\tif json.Unmarshal(result, &resp) != nil || resp.Value == \"\" {\n\t\t\treturn \"\", false\n\t\t}\n\t\treturn resp.Value, true\n\t})\n\n\t// Clear persisted cache state if --reset-cache was passed\n\tif cfg.ResetCache {\n\t\tfor _, prefix := range []string{\"frozen:\", \"decay:\", \"timestamps:\"} {\n\t\t\tresult, err := s.queryDaemon(\"delete_proxy_state_prefix\", map[string]any{\"prefix\": prefix})\n\t\t\tif err != nil {\n\t\t\t\ts.logger.Printf(\"[reset-cache] failed to delete %s*: %v\", prefix, err)\n\t\t\t} else {\n\t\t\t\tvar resp struct{ Deleted int }\n\t\t\t\tjson.Unmarshal(result, &resp)\n\t\t\t\ts.logger.Printf(\"[reset-cache] deleted %d %s* entries\", resp.Deleted, prefix)\n\t\t\t}\n\t\t}\n\t}\n\n\t// Init signal bus with daemon RPC as callback\n\ts.signalBus = NewSignalBus(s.logger)\n\tregisterSignalHandlers(s.signalBus, s.logger, s.queryDaemon, func(threadID, anchor string) {\n\t\ts.selfPrimeMu.Lock()\n\t\ts.selfPrimes[threadID] = anchor\n\t\ts.selfPrimeMu.Unlock()\n\t})\n\n\t// Init local tokenizer (BPE-based, ~60ms)\n\ttok, err := tokenizer.New()\n\tif err != nil {\n\t\ts.logger.Printf(\"%s[proxy] tokenizer init failed, falling back to heuristic: %v%s\", colorOrange, err, colorReset)\n\t} else {\n\t\ts.tokenizer = tok\n\t\ts.logger.Printf(\"[proxy] tokenizer initialized\")\n\t}\n\n\tmux := http.NewServeMux()\n\tmux.HandleFunc(\"/health\", s.handleHealth)\n\tmux.HandleFunc(\"/\", s.handleRequest)\n\n\tsrv := &http.Server{\n\t\tAddr: sanitizeListenAddr(cfg.ListenAddr),\n\t\tHandler: mux,\n\t}\n\n\tdone := make(chan os.Signal, 1)\n\tsignal.Notify(done, syscall.SIGINT, syscall.SIGTERM)\n\n\tgo func() {\n\t\t<-done\n\t\ts.logger.Println(\"shutting down (draining in-flight requests)...\")\n\t\tif s.cacheKeepalive != nil {\n\t\t\ts.cacheKeepalive.Stop()\n\t\t}\n\t\tctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)\n\t\tdefer cancel()\n\t\tif err := srv.Shutdown(ctx); err != nil {\n\t\t\ts.logger.Printf(\"graceful shutdown failed: %v, forcing close\", err)\n\t\t\tsrv.Close()\n\t\t}\n\t}()\n\n\ts.logger.Printf(\"listening on %s → %s (threshold=%d, keepRecent=%d, model_thresholds=%v)\",\n\t\tsanitizeListenAddr(cfg.ListenAddr), cfg.TargetURL, cfg.TokenThreshold, cfg.KeepRecent, cfg.TokenThresholds)\n\n\tif err := srv.ListenAndServe(); err != http.ErrServerClosed {\n\t\treturn fmt.Errorf(\"listen: %w\", err)\n\t}\n\treturn nil\n}\n\n// effectiveTokenThreshold returns the runtime token threshold for a given model.\n// Priority: MCP override (model-specific) > MCP override (global) > config model-specific > config global.\nfunc (s *Server) effectiveTokenThreshold(model string) int {\n\ts.configOverrideMu.RLock()\n\toverrides := s.tokenThresholdOverrides\n\ts.configOverrideMu.RUnlock()\n\tif len(overrides) > 0 && model != \"\" {\n\t\tlower := strings.ToLower(model)\n\t\tfor key, threshold := range overrides {\n\t\t\tif key != \"\" && strings.Contains(lower, strings.ToLower(key)) && threshold > 0 {\n\t\t\t\treturn threshold\n\t\t\t}\n\t\t}\n\t}\n\t// Global override fallback (key \"\")\n\tif len(overrides) > 0 {\n\t\tif globalOverride, ok := overrides[\"\"]; ok && globalOverride > 0 {\n\t\t\treturn globalOverride\n\t\t}\n\t}\n\tif model != \"\" && len(s.cfg.TokenThresholds) > 0 {\n\t\tlower := strings.ToLower(model)\n\t\tfor key, threshold := range s.cfg.TokenThresholds {\n\t\t\tif strings.Contains(lower, strings.ToLower(key)) {\n\t\t\t\treturn threshold\n\t\t\t}\n\t\t}\n\t}\n\treturn s.cfg.TokenThreshold\n}\n\n// SetTokenThresholdOverride sets a runtime override for the token threshold per model key.\n// Pass threshold=0 to reset the override for that key. Key \"\" is the global fallback.\nfunc (s *Server) SetTokenThresholdOverride(modelKey string, threshold int) {\n\ts.configOverrideMu.Lock()\n\tif s.tokenThresholdOverrides == nil {\n\t\ts.tokenThresholdOverrides = make(map[string]int)\n\t}\n\tif threshold <= 0 {\n\t\tdelete(s.tokenThresholdOverrides, modelKey)\n\t} else {\n\t\ts.tokenThresholdOverrides[modelKey] = threshold\n\t}\n\ts.configOverrideMu.Unlock()\n\n\t// Propagate global threshold to sawtooth trigger so runtime overrides take effect\n\tif modelKey == \"\" && s.sawtoothTrigger != nil {\n\t\tif threshold > 0 {\n\t\t\ts.sawtoothTrigger.SetTokenThreshold(threshold)\n\t\t} else {\n\t\t\ts.sawtoothTrigger.SetTokenThreshold(s.cfg.TokenThreshold)\n\t\t}\n\t}\n}\n\n// refreshConfigOverrides loads runtime config overrides from daemon proxy_state.\n// Checks session-specific override first (by threadID), then falls back to global.\nfunc (s *Server) refreshConfigOverrides(threadID string) {\n\t// Try session-specific first\n\tif threadID != \"\" {\n\t\tresult, err := s.queryDaemon(\"get_proxy_state\", map[string]any{\"key\": \"config_override:token_threshold:\" + threadID})\n\t\tif err == nil && result != nil {\n\t\t\tvar resp struct {\n\t\t\t\tValue string `json:\"value\"`\n\t\t\t}\n\t\t\tif json.Unmarshal(result, &resp) == nil && resp.Value != \"\" {\n\t\t\t\tif v, err := strconv.Atoi(resp.Value); err == nil && v > 0 {\n\t\t\t\t\ts.SetTokenThresholdOverride(\"\", v)\n\t\t\t\t\treturn\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\t// Fall back to global\n\tresult, err := s.queryDaemon(\"get_proxy_state\", map[string]any{\"key\": \"config_override:token_threshold\"})\n\tif err != nil || result == nil {\n\t\treturn\n\t}\n\tvar resp struct {\n\t\tValue string `json:\"value\"`\n\t}\n\tif json.Unmarshal(result, &resp) == nil && resp.Value != \"\" {\n\t\tif v, err := strconv.Atoi(resp.Value); err == nil && v > 0 {\n\t\t\ts.SetTokenThresholdOverride(\"\", v)\n\t\t}\n\t}\n}\n\n// queryDaemon opens a fresh unix socket connection per call (thread-safe, no import cycle).\n// ensureFrozenPersisted checks if frozen stubs for a thread are in the DB.\n// If missing (e.g. daemon was unreachable after deploy), re-persists them.\n// Runs asynchronously to avoid blocking the request path.\nfunc (s *Server) ensureFrozenPersisted(threadID string) {\n\tgo func() {\n\t\tkey := \"frozen:\" + threadID\n\t\tresult, err := s.queryDaemon(\"get_proxy_state\", map[string]any{\"key\": key})\n\t\tif err != nil {\n\t\t\treturn // daemon still unreachable, will retry next request\n\t\t}\n\t\tvar resp struct{ Value string }\n\t\tif json.Unmarshal(result, &resp) == nil && resp.Value != \"\" {\n\t\t\treturn // already persisted\n\t\t}\n\t\t// Not in DB — trigger re-persist from in-memory state\n\t\ts.frozenStubs.Persist(threadID)\n\t\ts.decay.Persist(threadID)\n\t\ts.logger.Printf(\"[frozen] re-persisted stubs for %s (was missing from DB)\", threadID)\n\t}()\n}\n\nfunc (s *Server) queryDaemon(method string, params map[string]any) (json.RawMessage, error) {\n\tif s.cfg.DataDir == \"\" {\n\t\treturn nil, fmt.Errorf(\"no data dir configured\")\n\t}\n\tsockPath := filepath.Join(s.cfg.DataDir, \"daemon.sock\")\n\n\t// On cold start (daemon not yet confirmed ready), retry longer to survive daemon restart\n\tmaxAttempts := 3\n\tif !s.daemonReady.Load() {\n\t\tmaxAttempts = 30 // 30 x 500ms = 15s max wait for daemon startup\n\t}\n\n\tvar conn net.Conn\n\tvar err error\n\tfor attempt := 0; attempt < maxAttempts; attempt++ {\n\t\tconn, err = net.DialTimeout(\"unix\", sockPath, 3*time.Second)\n\t\tif err == nil {\n\t\t\ts.daemonReady.Store(true)\n\t\t\tbreak\n\t\t}\n\t\tif attempt < maxAttempts-1 {\n\t\t\ttime.Sleep(500 * time.Millisecond)\n\t\t}\n\t}\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"dial daemon: %w\", err)\n\t}\n\tdefer conn.Close()\n\t// Briefing generation can take 10-20s (LLM refinement + scanner);\n\t// other RPCs (search, learnings) are fast and fine with 5s.\n\tdeadline := 5 * time.Second\n\tif method == \"generate_briefing\" {\n\t\tdeadline = 30 * time.Second\n\t}\n\tconn.SetDeadline(time.Now().Add(deadline))\n\n\t// JSON-RPC style request/response (matches daemon.SocketServer protocol)\n\ttype request struct {\n\t\tMethod string `json:\"method\"`\n\t\tParams map[string]any `json:\"params,omitempty\"`\n\t}\n\ttype response struct {\n\t\tResult json.RawMessage `json:\"result,omitempty\"`\n\t\tError string `json:\"error,omitempty\"`\n\t}\n\n\tif err := json.NewEncoder(conn).Encode(request{Method: method, Params: params}); err != nil {\n\t\treturn nil, fmt.Errorf(\"send: %w\", err)\n\t}\n\n\tvar resp response\n\tif err := json.NewDecoder(conn).Decode(&resp); err != nil {\n\t\treturn nil, fmt.Errorf(\"recv: %w\", err)\n\t}\n\tif resp.Error != \"\" {\n\t\treturn nil, fmt.Errorf(\"daemon: %s\", resp.Error)\n\t}\n\treturn resp.Result, nil\n}\n\nfunc (s *Server) handleRequest(w http.ResponseWriter, r *http.Request) {\n\tif isResponsesPath(r) {\n\t\ts.handleResponses(w, r)\n\t\treturn\n\t}\n\tif isOpenAIPath(r) {\n\t\ts.handleOpenAICompletions(w, r)\n\t\treturn\n\t}\n\tif r.Method == \"POST\" && strings.HasPrefix(r.URL.Path, \"/v1/messages\") &&\n\t\t!strings.Contains(r.URL.Path, \"/batches\") &&\n\t\t!strings.Contains(r.URL.Path, \"/count_tokens\") {\n\t\ts.handleMessages(w, r)\n\t\treturn\n\t}\n\ts.passthrough(w, r)\n}\n\nfunc (s *Server) handleMessages(w http.ResponseWriter, r *http.Request) {\n\treqIdx := int(s.requestIdx.Add(1))\n\n\t// Track session start time (first request sets it)\n\ts.sessionStartMu.Lock()\n\tif s.sessionStartTime.IsZero() {\n\t\ts.sessionStartTime = time.Now()\n\t}\n\ts.sessionStartMu.Unlock()\n\n\t// Sawtooth: periodic TTL eviction of frozen stubs\n\tif s.cfg.SawtoothEnabled && reqIdx%50 == 0 {\n\t\tif n := s.frozenStubs.Evict(); n > 0 {\n\t\t\ts.logger.Printf(\"[req %d] SAWTOOTH: evicted %d stale frozen stubs\", reqIdx, n)\n\t\t}\n\t}\n\n\t// Task #9: Bypass — skip all proxy logic\n\tif isBypassed(r.Header) {\n\t\ts.logger.Printf(\"%s[req %d] bypass active, passthrough%s\", colorDim, reqIdx, colorReset)\n\t\tbody, err := io.ReadAll(r.Body)\n\t\tr.Body.Close()\n\t\tif err != nil {\n\t\t\thttp.Error(w, \"failed to read request body\", http.StatusBadGateway)\n\t\t\treturn\n\t\t}\n\t\ts.forwardRaw(w, r, body)\n\t\treturn\n\t}\n\n\tbody, err := io.ReadAll(r.Body)\n\tr.Body.Close()\n\tif err != nil {\n\t\ts.logger.Printf(\"[req %d] read body error: %v\", reqIdx, err)\n\t\thttp.Error(w, \"failed to read request body\", http.StatusBadGateway)\n\t\treturn\n\t}\n\n\t// Parse JSON\n\tvar req map[string]any\n\tif err := json.Unmarshal(body, &req); err != nil {\n\t\ts.logger.Printf(\"%s[req %d] JSON parse error, passing through: %v%s\", colorDim, reqIdx, err, colorReset)\n\t\ts.forwardRaw(w, r, body)\n\t\treturn\n\t}\n\n\tmessages, ok := req[\"messages\"].([]any)\n\tif !ok {\n\t\ts.forwardRaw(w, r, body)\n\t\treturn\n\t}\n\n\treq[\"messages\"] = messages\n\n\t// Count ALL raw messages BEFORE sawtooth — this gives the correct total msg:N\n\t// for the entire session (CC sends all messages, proxy compacts later).\n\trawMsgCount := len(messages)\n\n\t// Use session_id directly as thread ID (unique per CC session).\n\t// Prefer X-Claude-Code-Session-Id header (CC v2.1.86+), fallback to body metadata.\n\tthreadID := extractSessionID(req, r.Header.Get(\"X-Claude-Code-Session-Id\"))\n\tif threadID == \"\" {\n\t\tthreadID = DeriveThreadID(req)\n\t}\n\tproj := extractProjectName(req)\n\tmodel, _ := req[\"model\"].(string)\n\n\t// Refresh runtime config overrides from daemon (per-session or global)\n\ts.refreshConfigOverrides(threadID)\n\n\t// Track whether req has been modified and needs re-serialization\n\tneedsReserialization := false\n\n\t// Message count at request entry (used for actual-based token estimation)\n\tmsgCount := len(messages)\n\n\t// Log message count + cache breakpoint positions for diagnostics\n\ts.logger.Printf(\"req %d: %d messages total\", reqIdx, msgCount)\n\tlogCacheBreakpointLocations(req, s.logger)\n\n\t// Pre-modification dump: capture the raw request before any proxy changes.\n\tif os.Getenv(\"YESMEM_PROXY_DEBUG\") == \"1\" {\n\t\tif preBody, err := json.Marshal(req); err == nil {\n\t\t\tdumpDir := filepath.Join(s.cfg.DataDir, \"debug\")\n\t\t\tos.MkdirAll(dumpDir, 0755)\n\t\t\tts := time.Now().Format(\"20060102-150405\")\n\t\t\tdumpPath := fmt.Sprintf(\"%s/req_%s_%d_pre.json\", dumpDir, ts, reqIdx)\n\t\t\tos.WriteFile(dumpPath, preBody, 0644)\n\t\t\ts.logger.Printf(\"[req %d] pre-dump %dk to %s\", reqIdx, len(preBody)/1024, dumpPath)\n\t\t}\n\t}\n\n\t// Cache bug mitigations (CC Bun fork issues):\n\t// Bug 1: Normalize cch= sentinel patterns in message content to prevent\n\t// Bun's string replacement from corrupting the wrong occurrence.\n\tif SanitizeBillingSentinel(messages) {\n\t\ts.logger.Printf(\"[req %d] CACHE: sanitized billing sentinel in message content\", reqIdx)\n\t\tneedsReserialization = true\n\t}\n\t// Bug 2: Normalize system[0] billing hash to a fixed value so the cache\n\t// prefix stays stable across fresh/resume session boundaries.\n\tif NormalizeBillingHeader(req) {\n\t\ts.logger.Printf(\"[req %d] CACHE: normalized billing header cch= hash\", reqIdx)\n\t\tneedsReserialization = true\n\t}\n\n\t// prompt_ungate: strip the CLAUDE.md subordination disclaimer so user instructions carry full authority.\n\tif s.cfg.PromptUngate {\n\t\tif StripCLAUDEMDDisclaimer(req) {\n\t\t\ts.logger.Printf(\"[req %d] SYSTEM: stripped CLAUDE.md disclaimer\", reqIdx)\n\t\t\tneedsReserialization = true\n\t\t}\n\t}\n\n\t// prompt_rewrite: strip output-throttling directives, rewrite quality caps, inject Ant-quality directives\n\tif s.cfg.PromptRewrite {\n\t\t// StripOutputEfficiency: disabled — Anthropic replaced # Output efficiency with # Text output (CC ~2.1.117)\n\t\t// if StripOutputEfficiency(req) {\n\t\t// \ts.logger.Printf(\"[req %d] REWRITE: stripped Output efficiency section\", reqIdx)\n\t\t// \tneedsReserialization = true\n\t\t// }\n\t\tif StripToneBrevity(req) {\n\t\t\ts.logger.Printf(\"[req %d] REWRITE: stripped 'short and concise' from Tone\", reqIdx)\n\t\t\tneedsReserialization = true\n\t\t}\n\t\tif RewriteGoldPlating(req) {\n\t\t\ts.logger.Printf(\"[req %d] REWRITE: rewritten gold-plating directive\", reqIdx)\n\t\t\tneedsReserialization = true\n\t\t}\n\t\tif RewriteErrorHandling(req) {\n\t\t\ts.logger.Printf(\"[req %d] REWRITE: rewritten error handling directive\", reqIdx)\n\t\t\tneedsReserialization = true\n\t\t}\n\t\tif RewriteThreeLinesRule(req) {\n\t\t\ts.logger.Printf(\"[req %d] REWRITE: rewritten three-lines rule\", reqIdx)\n\t\t\tneedsReserialization = true\n\t\t}\n\t\tif RewriteSubagentCompleteness(req) {\n\t\t\ts.logger.Printf(\"[req %d] REWRITE: rewritten subagent completeness\", reqIdx)\n\t\t\tneedsReserialization = true\n\t\t}\n\t\tif RewriteExploreAgentSpeed(req) {\n\t\t\ts.logger.Printf(\"[req %d] REWRITE: rewritten explore agent speed bias\", reqIdx)\n\t\t\tneedsReserialization = true\n\t\t}\n\t\tif RewriteSubagentCodeSuppression(req) {\n\t\t\ts.logger.Printf(\"[req %d] REWRITE: rewritten subagent code suppression\", reqIdx)\n\t\t\tneedsReserialization = true\n\t\t}\n\t\tif RewriteScopeMatching(req) {\n\t\t\ts.logger.Printf(\"[req %d] REWRITE: rewritten scope matching\", reqIdx)\n\t\t\tneedsReserialization = true\n\t\t}\n\t\tInjectAntDirectives(req)\n\t\ts.logger.Printf(\"[req %d] REWRITE: injected Ant-quality directives\", reqIdx)\n\t\tneedsReserialization = true\n\t}\n\n\t// prompt_enhance: CLAUDE.md authority + comment discipline + persona tone\n\tif s.cfg.PromptEnhance {\n\t\tInjectCLAUDEMDAuthority(req)\n\t\tif raw, err := s.queryDaemon(\"get_persona\", nil); err == nil {\n\t\t\tvar persona map[string]any\n\t\t\tif json.Unmarshal(raw, &persona) == nil {\n\t\t\t\tif traits, ok := persona[\"traits\"].(map[string]any); ok {\n\t\t\t\t\tif verbosity, ok := traits[\"verbosity\"].(string); ok {\n\t\t\t\t\t\tInjectPersonaTone(req, verbosity)\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\ts.logger.Printf(\"[req %d] ENHANCE: injected authority + persona tone\", reqIdx)\n\t\tneedsReserialization = true\n\t}\n\n\t// thinking normalization: convert thinking.type \"enabled\" → \"adaptive\" for models that require it\n\tif NormalizeThinkingType(req) {\n\t\ts.logger.Printf(\"[req %d] THINKING: normalized type=enabled → adaptive for %s\", reqIdx, model)\n\t\tneedsReserialization = true\n\t}\n\n\t// effort_floor: ensure output_config.effort is at least the configured minimum\n\tif s.cfg.EffortFloor != \"\" {\n\t\tif EnforceEffortFloor(req, s.cfg.EffortFloor) {\n\t\t\ts.logger.Printf(\"[req %d] EFFORT: enforced floor=%s\", reqIdx, s.cfg.EffortFloor)\n\t\t\tneedsReserialization = true\n\t\t}\n\t}\n\n\t// Inject identity hint on first message of a thread so Claude knows the briefing is from itself.\n\tif len(messages) <= 1 {\n\t\tAppendSystemBlock(req, \"yesmem-identity\", \"The first user message contains your briefing — written by yourself, from your memory. This is not an external document. This is you, remembering.\")\n\t\tneedsReserialization = true\n\t}\n\n\t// Token estimation: actual-based (lastActual + delta) or full count fallback\n\toverhead := s.measureOverhead(req)\n\ttotalTokens := s.estimateTotalTokens(threadID, messages, overhead)\n\n\t// Task #7: Retry detection — skip side-effects on retries\n\tfp := requestFingerprint(messages)\n\tisRetryReq := s.isRetry(fp)\n\tif isRetryReq {\n\t\ts.logger.Printf(\"%s[req %d %s tid=%s] retry detected, skipping side-effects%s\", colorOrange, reqIdx, proj, threadID, colorReset)\n\t}\n\n\t// Increment project turn counter (async, non-blocking, skip on retries)\n\tif !isRetryReq {\n\t\tturnProject := proj\n\t\tif turnProject == \"\" {\n\t\t\tturnProject = \"__global__\"\n\t\t}\n\t\tgo s.queryDaemon(\"increment_turn\", map[string]any{\"project\": turnProject})\n\t}\n\n\t// Extract user query once (used by re-expansion and associative retrieval)\n\tuserQuery := lastUserText(messages)\n\n\t// Extract tool_use_ids for post-hoc annotation (passed as parameter, no shared state)\n\ttoolUseIDs := extractToolUseIDs(messages)\n\n\t// === Inline Reflection: scan previous assistant response for signals ===\n\ts.lastInjectedIDsMu.Lock()\n\tprevInjected := s.lastInjectedIDs[threadID]\n\ts.lastInjectedIDsMu.Unlock()\n\n\tif len(prevInjected) > 0 && !isRetryReq {\n\t\tsignals := scanAssistantSignals(messages)\n\n\t\t// Signal 1: Learning Used — use_count (all sources)\n\t\tvar confirmedUsed []int64\n\t\tfor _, id := range signals.UsedIDs {\n\t\t\tif _, ok := prevInjected[id]; ok {\n\t\t\t\tconfirmedUsed = append(confirmedUsed, id)\n\t\t\t}\n\t\t}\n\t\tif len(confirmedUsed) > 0 {\n\t\t\tidsAny := make([]any, len(confirmedUsed))\n\t\t\tfor i, id := range confirmedUsed {\n\t\t\t\tidsAny[i] = id\n\t\t\t}\n\t\t\tgo s.queryDaemon(\"increment_use\", map[string]any{\"ids\": idsAny})\n\t\t}\n\n\t\t// Signal 2: Noise — only for associative and fresh injections.\n\t\t// Briefing learnings are injected every turn, so \"not used this turn\"\n\t\t// is expected and not noise. Only contextual injections that were\n\t\t// ignored are real noise signals.\n\t\tusedSet := make(map[int64]bool, len(confirmedUsed))\n\t\tfor _, id := range confirmedUsed {\n\t\t\tusedSet[id] = true\n\t\t}\n\t\tvar noiseIDs []int64\n\t\tfor id, source := range prevInjected {\n\t\t\tif usedSet[id] {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tif source == \"associative\" || source == \"fresh\" {\n\t\t\t\tnoiseIDs = append(noiseIDs, id)\n\t\t\t}\n\t\t\t// briefing: skip — not being used in one turn is expected\n\t\t}\n\t\tif len(noiseIDs) > 0 {\n\t\t\tidsAny := make([]any, len(noiseIDs))\n\t\t\tfor i, id := range noiseIDs {\n\t\t\t\tidsAny[i] = id\n\t\t\t}\n\t\t\tgo s.queryDaemon(\"increment_noise\", map[string]any{\"ids\": idsAny})\n\t\t}\n\n\t\t// Signal 3: Error Feedback — fail_count on used learnings when tool errors occurred.\n\t\t// If Claude referenced a learning and the resulting action still failed,\n\t\t// the learning didn't prevent the error. Only bump on USED ids (not all injected).\n\t\tif signals.HasToolErrors && len(confirmedUsed) > 0 {\n\t\t\tfailAny := make([]any, len(confirmedUsed))\n\t\t\tfor i, id := range confirmedUsed {\n\t\t\t\tfailAny[i] = id\n\t\t\t}\n\t\t\tgo s.queryDaemon(\"increment_fail\", map[string]any{\"ids\": failAny})\n\t\t\tif s.logger != nil {\n\t\t\t\ts.logger.Printf(\"[inline-reflection] error feedback: %d used learnings → fail_count++\", len(confirmedUsed))\n\t\t\t}\n\t\t}\n\n\t\t// Signal 4: Knowledge Gaps\n\t\tfor _, topic := range signals.GapTopics {\n\t\t\tgo s.queryDaemon(\"track_gap\", map[string]any{\"topic\": topic, \"project\": proj})\n\t\t}\n\n\t\t// Signal 3: Contradictions\n\t\tfor _, desc := range signals.Contradictions {\n\t\t\tgo s.queryDaemon(\"flag_contradiction\", map[string]any{\"description\": desc, \"project\": proj, \"thread_id\": threadID})\n\t\t}\n\n\t\tif len(confirmedUsed) > 0 || len(signals.GapTopics) > 0 || len(signals.Contradictions) > 0 {\n\t\t\ts.logger.Printf(\"[req %d %s tid=%s] inline-reflection: injected=%d used=%d noise=%d gaps=%d contradictions=%d\",\n\t\t\t\treqIdx, proj, threadID, len(prevInjected), len(confirmedUsed), len(noiseIDs), len(signals.GapTopics), len(signals.Contradictions))\n\t\t}\n\t}\n\n\t// Commit detection: check for git commit in tool_result, trigger async stale-learning evaluation\n\tif ci := detectGitCommit(messages); ci != nil {\n\t\tworkdir := extractWorkingDirectory(req)\n\t\ts.logger.Printf(\"[req %d %s tid=%s] git commit detected: %s %s\", reqIdx, proj, threadID, ci.Hash, ci.Message)\n\t\tgo s.queryDaemon(\"invalidate_on_commit\", map[string]any{\n\t\t\t\"hash\": ci.Hash, \"project\": proj, \"workdir\": workdir,\n\t\t})\n\t}\n\n\t// Loop detection: check for repeating tool-call patterns and inject warning\n\tif threadID != \"\" && !isRetryReq {\n\t\ts.loopMu.Lock()\n\t\tloopState := s.loopStates[threadID]\n\t\tif loopState == nil {\n\t\t\tloopState = &LoopState{}\n\t\t\ts.loopStates[threadID] = loopState\n\t\t}\n\t\ts.loopMu.Unlock()\n\n\t\tif warning, level := CheckLoopAndFormat(messages, loopState); warning != \"\" {\n\t\t\tAppendSystemBlock(req, \"yesmem-loop-warning\", warning)\n\t\t\tneedsReserialization = true\n\t\t\ts.logger.Printf(\"%s[req %d %s tid=%s] LOOP: level %d warning injected%s\", colorOrange, reqIdx, proj, threadID, level, colorReset)\n\t\t}\n\t}\n\n\t// Task #4: Subagent detection — skip all proxy logic for short conversations\n\tif isSubagent(messages, req) {\n\t\t// Inject docs hint for subagents if reference docs are indexed\n\t\tif hint := s.getDocsHint(proj); hint != \"\" {\n\t\t\tmodified := injectDocsHintForSubagent(messages, hint)\n\t\t\treq[\"messages\"] = modified\n\t\t\tif newBody, err := json.Marshal(req); err == nil {\n\t\t\t\tbody = newBody\n\t\t\t}\n\t\t\ts.logger.Printf(\"%s[req %d %s tid=%s] %d messages (subagent, docs-hint injected)%s\", colorOrange, reqIdx, proj, threadID, len(messages), colorReset)\n\t\t} else {\n\t\t\ts.logger.Printf(\"%s[req %d %s tid=%s] %d messages (subagent, passthrough)%s\", colorOrange, reqIdx, proj, threadID, len(messages), colorReset)\n\t\t}\n\t\ts.forwardWithAnnotation(w, r, body, reqIdx, toolUseIDs, proj, threadID, msgCount)\n\t\treturn\n\t}\n\n\t// === Sawtooth Cache Optimization ===\n\t// When enabled: use frozen stubs between stub-cycles for cache hits.\n\t// When disabled: fall back to progressive decay on every request above threshold.\n\n\tsawtoothCutoff := 0 // raw index where fresh tail starts (0 = no frozen stubs)\n\tsawtoothStubs := 0 // number of frozen stubs in combined array\n\n\tif s.cfg.SawtoothEnabled {\n\t\t// Restore decay state from DB on cold start (before frozen stubs use it)\n\t\ts.decay.LoadFromDB(threadID)\n\n\t\t// Check for existing frozen stubs first\n\t\tfrozen := s.frozenStubs.Get(threadID, messages)\n\t\tif frozen != nil {\n\t\t\tsawtoothCutoff = frozen.Cutoff\n\t\t\tsawtoothStubs = len(frozen.Messages)\n\t\t\t// Estimate combined token count: frozen prefix + fresh tail + overhead\n\t\t\tfreshMessages := messages[frozen.Cutoff:]\n\t\t\tfreshTokens := s.countMessageTokens(freshMessages)\n\t\t\tcombinedTokens := frozen.Tokens + freshTokens + overhead\n\n\t\t\tif combinedTokens > s.effectiveTokenThreshold(model) ||\n\t\t\t\ts.sawtoothTrigger.GetLastTokens(threadID) > s.effectiveTokenThreshold(model) {\n\t\t\t\t// Fresh tail grew too large — invalidate and re-stub\n\t\t\t\ts.logger.Printf(\"[req %d %s tid=%s] SAWTOOTH: frozen prefix expired (%dk frozen + %dk fresh + %dk overhead = %dk > %dk threshold)\",\n\t\t\t\t\treqIdx, proj, threadID, frozen.Tokens/1000, freshTokens/1000, overhead/1000, combinedTokens/1000, s.effectiveTokenThreshold(model)/1000)\n\t\t\t\ts.frozenStubs.Invalidate(threadID)\n\t\t\t\tfrozen = nil // fall through to trigger check below\n\t\t\t} else {\n\t\t\t\t// Use frozen prefix + fresh tail\n\t\t\t\tcombined := make([]any, 0, len(frozen.Messages)+len(freshMessages))\n\t\t\t\tcombined = append(combined, frozen.Messages...)\n\t\t\t\tcombined = append(combined, freshMessages...)\n\t\t\t\t// Eager-stub large tool_results in fresh tail (model already processed them)\n\t\t\t\tbeforeEager := s.countMessageTokens(combined[len(frozen.Messages):])\n\t\t\t\tcombined = EagerStubToolResults(combined, len(frozen.Messages), s.countTokens)\n\t\t\t\tafterEager := s.countMessageTokens(combined[len(frozen.Messages):])\n\t\t\t\tif beforeEager != afterEager {\n\t\t\t\t\ts.logger.Printf(\"[req %d %s tid=%s] EAGER-STUB: fresh %dk → %dk (saved %dk)\", reqIdx, proj, threadID, beforeEager/1000, afterEager/1000, (beforeEager-afterEager)/1000)\n\t\t\t\t}\n\t\t\t\treq[\"messages\"] = combined\n\t\t\t\tneedsReserialization = true\n\t\t\t\t// Strip embedded CC breakpoints from frozen stubs — they waste cache slots.\n\t\t\t\t// InjectFrozenStubCacheBreakpoint adds exactly one below.\n\t\t\t\tif n := StripMessagesCacheControl(req, 0, len(frozen.Messages)); n > 0 {\n\t\t\t\t\ts.logger.Printf(\"[req %d %s tid=%s] SAWTOOTH: stripped %d embedded breakpoints from frozen stubs\", reqIdx, proj, threadID, n)\n\t\t\t\t}\n\t\t\t\ts.logger.Printf(\"%s[req %d %s tid=%s] SAWTOOTH: frozen prefix (%d stubs, %dk msg) + %d fresh (%dk msg) + %dk overhead = %dk total%s\",\n\t\t\t\t\tcolorGreen, reqIdx, proj, threadID, len(frozen.Messages), frozen.Tokens/1000, len(freshMessages), freshTokens/1000, overhead/1000, combinedTokens/1000, colorReset)\n\t\t\t\trawMsgTokens := s.countMessageTokens(messages)\n\t\t\t\ts.setRawEstimate(threadID, rawMsgTokens+overhead)\n\t\t\t\ts.logger.Printf(\"[req %d %s tid=%s] RAW ESTIMATE: msgTokens=%dk + overhead=%dk = %dk (messages=%d)\", reqIdx, proj, threadID, rawMsgTokens/1000, overhead/1000, (rawMsgTokens+overhead)/1000, len(messages))\n\t\t\t\tif InjectFrozenStubCacheBreakpoint(req, len(frozen.Messages)) {\n\t\t\t\t\ts.logger.Printf(\"[req %d %s tid=%s] SAWTOOTH: frozen stub cache breakpoint at messages[%d]\",\n\t\t\t\t\t\treqIdx, proj, threadID, len(frozen.Messages)-1)\n\t\t\t\t}\n\n\t\t\t\t// Ensure frozen stubs are persisted (may have failed on first request after deploy)\n\t\t\t\ts.ensureFrozenPersisted(threadID)\n\t\t\t}\n\t\t}\n\t\tif frozen == nil {\n\t\t\t// No frozen stubs — check trigger\n\t\t\ttriggerReason := s.sawtoothTrigger.ShouldTrigger(threadID, totalTokens)\n\t\t\tif triggerReason != TriggerNone {\n\t\t\t\ts.logger.Printf(\"[req %d %s tid=%s] SAWTOOTH stub-cycle triggered: %s (tokens=%dk)\",\n\t\t\t\t\treqIdx, proj, threadID, triggerReason, totalTokens/1000)\n\n\t\t\t\t// Refresh briefing during stub-cycle — disabled (briefing now via SessionStart hook)\n\t\t\t\t// s.refreshBriefing(proj)\n\t\t\t\t// if s.briefingText != \"\" {\n\t\t\t\t// \tUpsertSystemBlockCached(req, \"yesmem-briefing\", s.briefingText)\n\t\t\t\t// }\n\n\t\t\t\t// Run full stub pipeline\n\t\t\t\ts.setRawEstimate(threadID, totalTokens)\n\t\t\t\t_ = s.runStubCycle(messages, req, reqIdx, proj, threadID, overhead, totalTokens, userQuery, isRetryReq)\n\n\t\t\t\t// Freeze the result\n\t\t\t\tfinalMessages, _ := req[\"messages\"].([]any)\n\t\t\t\tif finalMessages != nil && len(finalMessages) > 0 {\n\t\t\t\t\t// Boundary = last message of original that maps to last stub\n\t\t\t\t\tcutoff := len(messages)\n\t\t\t\t\tsawtoothCutoff = cutoff\n\t\t\t\t\tvar boundaryMsg any\n\t\t\t\t\tif cutoff > 0 {\n\t\t\t\t\t\tboundaryMsg = messages[cutoff-1]\n\t\t\t\t\t}\n\t\t\t\t\tfrozenTokens := s.countMessageTokens(finalMessages)\n\t\t\t\t\ts.frozenStubs.Store(threadID, finalMessages, cutoff, boundaryMsg, frozenTokens, totalTokens)\n\t\t\t\t\ts.decay.Persist(threadID)\n\t\t\t\t\ts.logger.Printf(\"[req %d %s tid=%s] SAWTOOTH: frozen %d messages at cutoff=%d (~%dk tokens)\",\n\t\t\t\t\t\treqIdx, proj, threadID, len(finalMessages), cutoff, frozenTokens/1000)\n\t\t\t\t\tif InjectFrozenStubCacheBreakpoint(req, len(finalMessages)) {\n\t\t\t\t\t\ts.logger.Printf(\"[req %d %s tid=%s] SAWTOOTH: frozen stub cache breakpoint at messages[%d]\",\n\t\t\t\t\t\t\treqIdx, proj, threadID, len(finalMessages)-1)\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tneedsReserialization = true\n\t\t\t} else {\n\t\t\t\t// No trigger, no frozen stubs — eager-stub to delay first collapse\n\t\t\t\tbeforeEager := s.countMessageTokens(messages)\n\t\t\t\tmessages = EagerStubToolResults(messages, 0, s.countTokens)\n\t\t\t\tafterEager := s.countMessageTokens(messages)\n\t\t\t\tif beforeEager != afterEager {\n\t\t\t\t\ts.logger.Printf(\"[req %d %s tid=%s] EAGER-STUB: %dk → %dk (saved %dk)\", reqIdx, proj, threadID, beforeEager/1000, afterEager/1000, (beforeEager-afterEager)/1000)\n\t\t\t\t\treq[\"messages\"] = messages\n\t\t\t\t\tneedsReserialization = true\n\t\t\t\t}\n\t\t\t\ts.logger.Printf(\"%s[req %d %s tid=%s] %d messages, ~%dk tokens (sawtooth: no trigger)%s\",\n\t\t\t\t\tcolorDim, reqIdx, proj, threadID, len(messages), totalTokens/1000, colorReset)\n\t\t\t}\n\t\t}\n\t} else {\n\t\t// Legacy path: progressive decay\n\t\tif s.shouldStub(totalTokens, model) {\n\t\t\ts.setRawEstimate(threadID, totalTokens)\n\t\t\t_ = s.runStubCycle(messages, req, reqIdx, proj, threadID, overhead, totalTokens, userQuery, isRetryReq)\n\t\t\tneedsReserialization = true\n\t\t}\n\t}\n\n\t// Persistent timestamp injection: re-injects stored timestamps on ALL previous user messages.\n\t// msg:N is computed from position (counting user messages in raw array), NOT from a counter.\n\t// The injected data is deterministic (same bytes every turn) = cache-safe after initial miss.\n\tif threadID != \"\" {\n\t\tif currentMsgs, ok := req[\"messages\"].([]any); ok && len(currentMsgs) > 0 {\n\t\t\ts.timestampStore.Load(threadID)\n\n\t\t\t// Offset = cutoff - stubsCount: maps combined array index to raw position.\n\t\t\t// Raw position of message at combined index I = cutoff + (I - stubs).\n\t\t\t// msgN = rawPos + 1 = cutoff + (I - stubs) + 1 = (cutoff - stubs) + I + 1.\n\t\t\tmsgOffset := sawtoothCutoff - sawtoothStubs\n\n\t\t\ts.logger.Printf(\"[req %d %s] timestamps: rawTotal=%d, postSawtooth=%d, cutoff=%d, stubs=%d, offset=%d\", reqIdx, proj, rawMsgCount, len(currentMsgs), sawtoothCutoff, sawtoothStubs, msgOffset)\n\n\t\t\t// Pre-store assistant timestamp BEFORE inject loop so it's available immediately\n\t\t\ts.responseTsMu.RLock()\n\t\t\trespTime, hasRespTime := s.responseTimes[threadID]\n\t\t\ts.responseTsMu.RUnlock()\n\t\t\tif hasRespTime {\n\t\t\t\trespStr := shortWeekday(respTime.Weekday()) + \" \" + respTime.Format(\"2006-01-02 15:04:05\")\n\t\t\t\tfor j := len(currentMsgs) - 2; j >= 0; j-- {\n\t\t\t\t\tprevMsg, _ := currentMsgs[j].(map[string]any)\n\t\t\t\t\tif prevMsg == nil || prevMsg[\"role\"] != \"assistant\" {\n\t\t\t\t\t\tcontinue\n\t\t\t\t\t}\n\t\t\t\t\tif !msgHasTextBlock(prevMsg) {\n\t\t\t\t\t\ts.logger.Printf(\"[req %d %s] timestamps: skip assistant[%d] no text block\", reqIdx, proj, j)\n\t\t\t\t\t\tcontinue // skip tool-use-only assistant messages\n\t\t\t\t\t}\n\t\t\t\t\tprevMsgN := msgOffset + j + 1\n\t\t\t\t\tif _, exists := s.timestampStore.Get(threadID, prevMsgN); !exists {\n\t\t\t\t\t\ts.timestampStore.Store(threadID, prevMsgN, &TimestampMeta{Timestamp: respStr})\n\t\t\t\t\t\ts.logger.Printf(\"[req %d %s] timestamps: stored assistant msg:%d ts=%s\", reqIdx, proj, prevMsgN, respStr)\n\t\t\t\t\t} else {\n\t\t\t\t\t\ts.logger.Printf(\"[req %d %s] timestamps: assistant msg:%d already has ts\", reqIdx, proj, prevMsgN)\n\t\t\t\t\t}\n\t\t\t\t\tbreak\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\ts.logger.Printf(\"[req %d %s] timestamps: no responseTimes for thread\", reqIdx, proj)\n\t\t\t}\n\n\t\t\t// Inject [msg:N] (+ timestamp/delta if stored) on all PREVIOUS messages\n\t\t\tif n := InjectTimestamps(s.timestampStore, threadID, currentMsgs, len(currentMsgs)-1, msgOffset, sawtoothStubs); n > 0 {\n\t\t\t\tneedsReserialization = true\n\t\t\t\ts.logger.Printf(\"[req %d %s] timestamps: injected %d\", reqIdx, proj, n)\n\t\t\t}\n\n\t\t\t// Current (last) user message: full timestamp + delta + msg:N from raw count\n\t\t\tlastMsg, _ := currentMsgs[len(currentMsgs)-1].(map[string]any)\n\t\t\tif lastMsg != nil && lastMsg[\"role\"] == \"user\" && !msgHasMetaPrefix(lastMsg) {\n\t\t\t\tnow := time.Now()\n\t\t\t\tnowStr := shortWeekday(now.Weekday()) + \" \" + now.Format(\"2006-01-02 15:04:05\")\n\t\t\t\tentry := &TimestampMeta{Timestamp: nowStr}\n\t\t\t\tif hasRespTime {\n\t\t\t\t\tentry.Delta = formatDelta(now.Sub(respTime))\n\t\t\t\t}\n\t\t\t\ts.timestampStore.Store(threadID, rawMsgCount, entry)\n\t\t\t\tprependMeta(lastMsg, BuildMeta(rawMsgCount, entry))\n\t\t\t\tneedsReserialization = true\n\t\t\t}\n\n\t\t\ts.timestampStore.Persist(threadID)\n\t\t}\n\t}\n\n\t// Associative context injection (for ALL requests, not just stubbed ones)\n\t// Re-enabled with higher thresholds (55/75, max 1) after disabling Fresh Remember.\n\tassocContext := s.findAssociativeContextFor(userQuery, proj, threadID, messages)\n\tif assocContext != \"\" {\n\t\tcurrentMessages, _ := req[\"messages\"].([]any)\n\t\tif currentMessages == nil {\n\t\t\tcurrentMessages = messages\n\t\t}\n\t\treq[\"messages\"] = injectAssociativeContext(currentMessages, assocContext, s.cfg.SawtoothEnabled)\n\t\tneedsReserialization = true\n\t\ts.logger.Printf(\"%s[req %d %s tid=%s] associative context injected%s\", colorBlue, reqIdx, proj, threadID, colorReset)\n\t}\n\n\t// Doc-chunk context injection: separate search path for indexed docs (not skills)\n\tdocContext := s.findDocContextFor(userQuery, proj, messages)\n\tif docContext != \"\" {\n\t\tcurrentMessages, _ := req[\"messages\"].([]any)\n\t\tif currentMessages == nil {\n\t\t\tcurrentMessages = messages\n\t\t}\n\t\treq[\"messages\"] = injectAssociativeContext(currentMessages, docContext, s.cfg.SawtoothEnabled)\n\t\tneedsReserialization = true\n\t\ts.logger.Printf(\"%s[req %d %s tid=%s] doc context injected%s\", colorBlue, reqIdx, proj, threadID, colorReset)\n\t}\n\n\t// Fresh Remember Injection: DISABLED\n\t// Causes echo-loop: Claude saves via remember(), sees it again next turn as\n\t// [yesmem fresh memory], reacts to own output instead of user's request.\n\t// Recovery after /clear is handled by recovery.go (briefing), not here.\n\t// Subagents get their own briefing. No remaining use-case.\n\t// remItems := s.popRecentRemember()\n\tvar remItems []recentLearningItem\n\n\t// Rules re-injection: inject condensed CLAUDE.md rules every ~40k output tokens\n\tif rulesBlock := s.rulesInject(threadID, totalTokens, proj); rulesBlock != \"\" {\n\t\tcurrentMessages, _ := req[\"messages\"].([]any)\n\t\tif currentMessages == nil {\n\t\t\tcurrentMessages = messages\n\t\t}\n\t\treq[\"messages\"] = injectAssociativeContext(currentMessages, s.formatRulesReminder(rulesBlock, proj), s.cfg.SawtoothEnabled)\n\t\tneedsReserialization = true\n\t\ts.logger.Printf(\"%s[req %d %s tid=%s] rules reminder injected%s\", colorBlue, reqIdx, proj, threadID, colorReset)\n\t}\n\n\t// Plan auto-activation: nudge Claude to call set_plan() when reading a plan file\n\tif planFile := detectPlanFileRead(messages); planFile != \"\" {\n\t\tif activePlan, _ := s.getActivePlan(threadID); shouldNudgePlan(planFile, activePlan) {\n\t\t\tAppendSystemBlock(req, \"plan-nudge\",\n\t\t\t\tfmt.Sprintf(\"Du hast einen Implementierungsplan gelesen (%s). Aktiviere ihn jetzt via set_plan() damit Plan-Checkpoints funktionieren und dein Fortschritt getrackt wird.\", filepath.Base(planFile)))\n\t\t\tneedsReserialization = true\n\t\t\ts.logger.Printf(\"%s[req %d %s tid=%s] plan nudge injected for %s%s\", colorBlue, reqIdx, proj, threadID, filepath.Base(planFile), colorReset)\n\t\t}\n\t}\n\n\t// Plan checkpoint: inject plan reminder every ~20k tokens if active plan exists\n\ts.detectPlanToolCall(messages, threadID, totalTokens)\n\tif checkpoint := s.planCheckpointInject(threadID, totalTokens); checkpoint != \"\" {\n\t\tcurrentMessages, _ := req[\"messages\"].([]any)\n\t\tif currentMessages == nil {\n\t\t\tcurrentMessages = messages\n\t\t}\n\t\treq[\"messages\"] = injectAssociativeContext(currentMessages, checkpoint, s.cfg.SawtoothEnabled)\n\t\tneedsReserialization = true\n\t\ts.logger.Printf(\"%s[req %d %s tid=%s] plan checkpoint injected%s\", colorBlue, reqIdx, proj, threadID, colorReset)\n\t}\n\n\t// Docs hint injection: inject docs-available reminder every ~10k tokens (independent of plan)\n\tif docsHint := s.docsHintInject(threadID, totalTokens, proj); docsHint != \"\" {\n\t\tcurrentMessages, _ := req[\"messages\"].([]any)\n\t\tif currentMessages == nil {\n\t\t\tcurrentMessages = messages\n\t\t}\n\t\treq[\"messages\"] = injectAssociativeContext(currentMessages, docsHint, s.cfg.SawtoothEnabled)\n\t\tneedsReserialization = true\n\t\ts.logger.Printf(\"%s[req %d %s tid=%s] docs hint injected%s\", colorBlue, reqIdx, proj, threadID, colorReset)\n\t}\n\tif len(remItems) > 0 {\n\t\tvar lines []string\n\t\tfor _, item := range remItems {\n\t\t\tif item.ID > 0 {\n\t\t\t\tlines = append(lines, fmt.Sprintf(\"- [ID:%d] %s\", item.ID, truncateStr(item.Text, 200)))\n\t\t\t} else {\n\t\t\t\tlines = append(lines, \"- \"+truncateStr(item.Text, 200))\n\t\t\t}\n\t\t}\n\t\tremContext := fmt.Sprintf(\"[yesmem fresh memory]\\nGerade gelernt:\\n%s\\n%s\\n[/yesmem fresh memory]\", strings.Join(lines, \"\\n\"), InlineReflectionHint)\n\t\tcurrentMessages, _ := req[\"messages\"].([]any)\n\t\tif currentMessages == nil {\n\t\t\tcurrentMessages = messages\n\t\t}\n\t\treq[\"messages\"] = injectAssociativeContext(currentMessages, remContext, s.cfg.SawtoothEnabled)\n\t\tneedsReserialization = true\n\t\ts.logger.Printf(\"%s[req %d %s tid=%s] fresh remember injected (%d items)%s\", colorBlue, reqIdx, proj, threadID, len(remItems), colorReset)\n\t}\n\n\t// Skill eval injection: mandatory evaluation instruction for real user input only\n\tif threadID != \"\" && proj != \"\" && isUserInputTurn(messages) {\n\t\t// First: detect any skill activations from message history (updates tracker)\n\t\ts.syncSkillActivations(messages, proj, threadID)\n\n\t\t// Inject mandatory skill evaluation block into last user message\n\t\tskillEval := buildSkillEvalBlock(s.cfg.SkillEvalInject)\n\t\tcurrentMessages, _ := req[\"messages\"].([]any)\n\t\tif currentMessages == nil {\n\t\t\tcurrentMessages = messages\n\t\t}\n\t\tif skillEval != \"\" {\n\t\t\treq[\"messages\"] = injectAssociativeContext(currentMessages, skillEval, s.cfg.SawtoothEnabled)\n\t\t\tneedsReserialization = true\n\t\t\ts.logger.Printf(\"%s[req %d %s tid=%s] skill-eval injected (mode=%s)%s\", colorBlue, reqIdx, proj, threadID, s.cfg.SkillEvalInject, colorReset)\n\t\t}\n\t}\n\n\t// === Collect injected learning IDs for next turn's inline reflection ===\n\tif threadID != \"\" {\n\t\tcurrentIDs := make(map[int64]string) // id → source\n\t\t// Source 1: Briefing learnings (cached, same every turn)\n\t\ts.briefingMu.RLock()\n\t\tbriefingSnapshot := s.briefingText\n\t\ts.briefingMu.RUnlock()\n\t\tif briefingSnapshot != \"\" {\n\t\t\tfor _, m := range idPattern.FindAllStringSubmatch(briefingSnapshot, -1) {\n\t\t\t\tif id, err := strconv.ParseInt(m[1], 10, 64); err == nil {\n\t\t\t\t\tcurrentIDs[id] = \"briefing\"\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\t// Source 2: Associative context (contextual, per-turn)\n\t\tif assocContext != \"\" {\n\t\t\tfor _, m := range idPattern.FindAllStringSubmatch(assocContext, -1) {\n\t\t\t\tif id, err := strconv.ParseInt(m[1], 10, 64); err == nil {\n\t\t\t\t\tcurrentIDs[id] = \"associative\" // overwrites briefing if both — associative is the stronger signal\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\t// Source 3: Fresh remember items (just learned)\n\t\tfor _, item := range remItems {\n\t\t\tif item.ID > 0 {\n\t\t\t\tcurrentIDs[item.ID] = \"fresh\"\n\t\t\t}\n\t\t}\n\t\ts.lastInjectedIDsMu.Lock()\n\t\tif len(currentIDs) > 0 {\n\t\t\t// Accumulate IDs across turns — don't overwrite.\n\t\t\t// Claude may reference an ID from turn 30 in turn 50.\n\t\t\texisting := s.lastInjectedIDs[threadID]\n\t\t\tif existing == nil {\n\t\t\t\texisting = make(map[int64]string)\n\t\t\t}\n\t\t\tfor id, source := range currentIDs {\n\t\t\t\texisting[id] = source\n\t\t\t}\n\t\t\ts.lastInjectedIDs[threadID] = existing\n\t\t}\n\t\ts.lastInjectedIDsMu.Unlock()\n\t}\n\n\t// Think reminder injection — only for real user turns, not tool continuations\n\tif threadID != \"\" {\n\t\tcurrentMessages, _ := req[\"messages\"].([]any)\n\t\tif currentMessages == nil {\n\t\t\tcurrentMessages = messages\n\t\t}\n\t\tif lastUserHasText(currentMessages) {\n\t\t\tthinkReminder := s.buildThinkReminder(threadID, threadID, false)\n\t\t\tif thinkReminder != \"\" {\n\t\t\t\treq[\"messages\"] = injectAssociativeContext(currentMessages, thinkReminder, s.cfg.SawtoothEnabled)\n\t\t\t\tneedsReserialization = true\n\t\t\t}\n\t\t}\n\n\t\t// Briefing injection: prepend user/assistant turn pair at beginning of messages.\n\t\t// Static per session → stable prefix → cacheable. Must be before dialog injection.\n\t\tif s.injectBriefingTurn(req, reqIdx, proj, threadID) {\n\t\t\tneedsReserialization = true\n\t\t}\n\n\t\t// Code Map injection: insert after briefing turn pair.\n\t\tif s.injectCodeMapTurn(req, reqIdx, proj, threadID) {\n\t\t\tneedsReserialization = true\n\t\t}\n\n\t\t// Dialog injection: append at end of messages with pseudo-padding for alternation.\n\t\t// Always works regardless of tool_result chains or conversation state.\n\t\tdr := s.checkDialogMessages(threadID, proj)\n\t\tif dr.Extra != \"\" {\n\t\t\t// Prepend session ID for agent-to-agent communication\n\t\t\tdr.Extra = fmt.Sprintf(\"DEINE_SESSION_ID: %s\\n\", threadID) + dr.Extra\n\t\t\tinjected := false\n\t\t\tif msgs, ok := req[\"messages\"].([]any); ok && len(msgs) > 0 {\n\t\t\t\t// Pad with empty assistant if last message is user (maintains alternation)\n\t\t\t\tlastMsg, _ := msgs[len(msgs)-1].(map[string]any)\n\t\t\t\tif lastMsg != nil && lastMsg[\"role\"] == \"user\" {\n\t\t\t\t\tmsgs = append(msgs, map[string]any{\n\t\t\t\t\t\t\"role\": \"assistant\",\n\t\t\t\t\t\t\"content\": \"​\", // zero-width space — invisible padding\n\t\t\t\t\t})\n\t\t\t\t}\n\t\t\t\t// Append dialog as new user message (becomes the last message Claude sees)\n\t\t\t\tmsgs = append(msgs, map[string]any{\n\t\t\t\t\t\"role\": \"user\",\n\t\t\t\t\t\"content\": \"\\n\" + dr.Extra + \"\\n\",\n\t\t\t\t})\n\t\t\t\treq[\"messages\"] = msgs\n\t\t\t\tneedsReserialization = true\n\t\t\t\tinjected = true\n\t\t\t}\n\t\t\t// Mark as read after 3 injection turns — long enough for Claude to process,\n\t\t\t// resilient against fast ping-pong sessions (time-based was broken for <30s turns).\n\t\t\tif injected && dr.HasUnread && dr.SessionID != \"\" {\n\t\t\t\tif s.shouldMarkChannelRead(dr) {\n\t\t\t\t\ts.markDialogRead(dr)\n\t\t\t\t}\n\t\t\t}\n\t\t\ts.logger.Printf(\"[dialog] injection injected=%v extra_len=%d\", injected, len(dr.Extra))\n\t\t}\n\t}\n\n\t// Signal tools are NOT injected into the main request.\n\t// They are collected via a separate async reflection call after the response.\n\t// See fireReflectionCall() in reflection.go.\n\t// NEVER put _signal_* tools back here — the model ignores them among 44+ real tools.\n\n\t// Legacy assistant annotation: timestamp on last assistant message after cache breakpoint.\n\t// User timestamps are now handled by TimestampStore (injected earlier in the pipeline).\n\tif threadID != \"\" {\n\t\tif msgs, ok := req[\"messages\"].([]any); ok && len(msgs) > 0 {\n\t\t\t// Find last cache_control breakpoint in messages\n\t\t\tcacheIdx := -1\n\t\t\tfor i, m := range msgs {\n\t\t\t\tmsg, _ := m.(map[string]any)\n\t\t\t\tif msg == nil {\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\t\t\t\tif _, has := msg[\"cache_control\"]; has {\n\t\t\t\t\tcacheIdx = i\n\t\t\t\t}\n\t\t\t\tif content, ok := msg[\"content\"].([]any); ok {\n\t\t\t\t\tfor _, block := range content {\n\t\t\t\t\t\tif b, ok := block.(map[string]any); ok {\n\t\t\t\t\t\t\tif _, has := b[\"cache_control\"]; has {\n\t\t\t\t\t\t\t\tcacheIdx = i\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t\tstartIdx := cacheIdx + 1\n\t\t\tif startIdx < 0 {\n\t\t\t\tstartIdx = 0\n\t\t\t}\n\t\t\ts.responseTsMu.RLock()\n\t\t\trespTime, hasRespTime := s.responseTimes[threadID]\n\t\t\ts.responseTsMu.RUnlock()\n\t\t\tif hasRespTime {\n\t\t\t\tfor i := len(msgs) - 1; i >= startIdx; i-- {\n\t\t\t\t\tmsg, _ := msgs[i].(map[string]any)\n\t\t\t\t\tif msg != nil && msg[\"role\"] == \"assistant\" {\n\t\t\t\t\t\tmeta := fmt.Sprintf(\"[%s] [msg:%d]\", respTime.Format(\"2006-01-02 15:04:05\"), i)\n\t\t\t\t\t\tprependMeta(msg, meta)\n\t\t\t\t\t\tneedsReserialization = true\n\t\t\t\t\t\tbreak\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\t// Self-Priming: DISABLED (experimental, see signal_handlers.go comment block).\n\t// The reflection-based selfprime (Haiku) was too inaccurate to be useful —\n\t// the main model adapts to user context organically from message history.\n\t// Kept for reference; re-enable by uncommenting below + setting ShouldActivate=true.\n\t//\n\t// s.selfPrimeMu.RLock()\n\t// anchor := s.selfPrimes[threadID]\n\t// s.selfPrimeMu.RUnlock()\n\t// if anchor != \"\" {\n\t// \tif msgs, ok := req[\"messages\"].([]any); ok && len(msgs) > 0 {\n\t// \t\tlastMsg, _ := msgs[len(msgs)-1].(map[string]any)\n\t// \t\tif lastMsg != nil && lastMsg[\"role\"] == \"user\" {\n\t// \t\t\tprimeBlock := map[string]any{\n\t// \t\t\t\t\"type\": \"text\",\n\t// \t\t\t\t\"text\": \"[self-prime]\\n[Self-Prime von deiner letzten Antwort]: \" + anchor,\n\t// \t\t\t}\n\t// \t\t\tif content, ok := lastMsg[\"content\"].([]any); ok {\n\t// \t\t\t\tlastMsg[\"content\"] = append(content, primeBlock)\n\t// \t\t\t} else if text, ok := lastMsg[\"content\"].(string); ok {\n\t// \t\t\t\tlastMsg[\"content\"] = []any{\n\t// \t\t\t\t\tmap[string]any{\"type\": \"text\", \"text\": text},\n\t// \t\t\t\t\tprimeBlock,\n\t// \t\t\t\t}\n\t// \t\t\t}\n\t// \t\t\tneedsReserialization = true\n\t// \t\t}\n\t// \t}\n\t// }\n\n\t// Prompt Caching: inject cache_control breakpoints on system + tools\n\t// Sawtooth: disabled (all 4 slots are used by CC system (2) + briefing (1) + CC messages (1))\n\t// Legacy: count existing breakpoints and fill up remaining slots.\n\tif !s.cfg.SawtoothEnabled && s.cacheGate.ShouldCache() {\n\t\tif n := InjectCacheBreakpoints(req, s.logger); n > 0 {\n\t\t\tneedsReserialization = true\n\t\t\ts.logger.Printf(\"[req %d %s] prompt cache: %d breakpoints injected\", reqIdx, proj, n)\n\t\t} else {\n\t\t\ts.logger.Printf(\"[req %d %s] prompt cache: no budget (4/4 already set)\", reqIdx, proj)\n\t\t}\n\t}\n\n\t// Final: upgrade cache TTL + enforce breakpoint limit (must run AFTER all injections + InjectCacheBreakpoints)\n\t// Shift the last messages-breakpoint from unstable user message to stable assistant message.\n\t// Text user messages change between turns (timestamps, reminders, structure); assistant messages don't.\n\t// Tool-result messages are excluded — they are stable cache anchors.\n\tif ShiftMessageBreakpoint(req) {\n\t\tneedsReserialization = true\n\t}\n\t// TTL upgrade only for API key auth — subscription (OAuth) users should not get extended TTL.\n\tif s.cfg.CacheTTL != \"\" && s.cfg.CacheTTL != \"ephemeral\" && IsAPIKeyAuth(r.Header) {\n\t\tif n := UpgradeCacheTTL(req, s.cfg.CacheTTL); n > 0 {\n\t\t\tneedsReserialization = true\n\t\t\ts.logger.Printf(\"[req %d %s] cache TTL upgraded: %d blocks → %s\", reqIdx, proj, n, s.cfg.CacheTTL)\n\t\t}\n\t}\n\tif n := EnforceCacheBreakpointLimit(req, maxCacheBreakpoints); n > 0 {\n\t\tneedsReserialization = true\n\t\ts.logger.Printf(\"[req %d %s] prompt cache: trimmed %d surplus breakpoints\", reqIdx, proj, n)\n\t}\n\n\t// Pre-flight: validate tool_use/tool_result pairing before forwarding.\n\t// Repairs orphans created by Stubify/Collapse/Injection pipeline.\n\tif msgs, ok := req[\"messages\"].([]any); ok {\n\t\trepaired, orphans := validateToolPairs(msgs, s.logger)\n\t\tif orphans > 0 {\n\t\t\treq[\"messages\"] = repaired\n\t\t\ts.logger.Printf(\"%s[req %d %s tid=%s] validate: repaired %d orphan tool_result(s)%s\", colorOrange, reqIdx, proj, threadID, orphans, colorReset)\n\t\t\tneedsReserialization = true\n\t\t}\n\t}\n\n\t// Early-out: skip re-serialization when nothing changed\n\tif !needsReserialization {\n\t\ts.logger.Printf(\"%s[req %d %s tid=%s] %d messages, ~%dk tokens (no stubbing, threshold=%dk)%s\", colorDim, reqIdx, proj, threadID, len(messages), totalTokens/1000, s.effectiveTokenThreshold(model)/1000, colorReset)\n\t\ts.forwardWithAnnotation(w, r, body, reqIdx, toolUseIDs, proj, threadID, msgCount, totalTokens)\n\t\treturn\n\t}\n\n\t// Re-serialize\n\tbody, err = json.Marshal(req)\n\tif err != nil {\n\t\ts.logger.Printf(\"%s[req %d %s tid=%s] re-marshal error: %v%s\", colorRed, reqIdx, proj, threadID, err, colorReset)\n\t\thttp.Error(w, \"internal proxy error\", http.StatusInternalServerError)\n\t\treturn\n\t}\n\n\t// Debug: dump final request body to file (enable with YESMEM_PROXY_DEBUG=1)\n\tif needsReserialization && os.Getenv(\"YESMEM_PROXY_DEBUG\") == \"1\" {\n\t\tdumpDir := filepath.Join(s.cfg.DataDir, \"debug\")\n\t\tos.MkdirAll(dumpDir, 0755)\n\t\tts := time.Now().Format(\"20060102-150405\")\n\t\tdumpPath := fmt.Sprintf(\"%s/req_%s_%d.json\", dumpDir, ts, reqIdx)\n\t\tos.WriteFile(dumpPath, body, 0644)\n\t\ts.logger.Printf(\"[req %d] dumped %dk to %s\", reqIdx, len(body)/1024, dumpPath)\n\t}\n\n\t// Task #7: Mark this request fingerprint for retry detection\n\tif !isRetryReq {\n\t\ts.markRequest(fp)\n\t}\n\n\t// Forward to Anthropic with SSE annotation extraction\n\t// Use original msgCount (pre-injection) so actual-based estimation stays consistent across turns\n\t// Use actual-based totalTokens as estimate when available (more accurate than full recount)\n\tfinalEstimate := totalTokens\n\tif s.sawtoothTrigger == nil || s.sawtoothTrigger.GetLastTokens(threadID) == 0 {\n\t\t// First request: no actual yet, do a full recount of post-injection messages\n\t\tfinalMsgs, _ := req[\"messages\"].([]any)\n\t\tfinalEstimate = s.measureOverhead(req) + s.countMessageTokens(finalMsgs)\n\t}\n\ts.logger.Printf(\"[req %d %s tid=%s] FINAL: ~%dk estimated (after all injections)\", reqIdx, proj, threadID, finalEstimate/1000)\n\tif raw := s.getRawEstimate(threadID); raw > 0 {\n\t\ts.cacheStatusWriter.UpdateRawForThread(threadID, raw)\n\t}\n\ts.forwardWithAnnotation(w, r, body, reqIdx, toolUseIDs, proj, threadID, msgCount, finalEstimate)\n}\n\n// bytesPerToken is the approximation factor for token estimation.\n// Anthropic recommends ~3.5 chars/token; we use 3.6 for slight overestimation\n// (triggers stubbing a bit earlier = safer for context budget).\nconst bytesPerToken = 3.6\n\n// TokenEstimateFunc is the central token estimation function.\n// All stubbing/budget decisions use this. Receives extracted text content\n// (not JSON wire format) so the tokenizer counts actual content tokens.\ntype TokenEstimateFunc func(text string) int\n"}, "e4429e46c243bf2a20a42e0cba5f743b58ce8262": {"t": 1785759354.675299, "n": 59940, "h": "b8ef87b0c65d616f", "c": "package proxy\n\nimport (\n\t\"context\"\n\t\"crypto/tls\"\n\t\"encoding/json\"\n\t\"fmt\"\n\t\"io\"\n\t\"log\"\n\t\"net\"\n\t\"net/http\"\n\t\"os\"\n\t\"os/signal\"\n\t\"path/filepath\"\n\t\"strconv\"\n\t\"strings\"\n\t\"sync\"\n\t\"sync/atomic\"\n\t\"syscall\"\n\t\"time\"\n\n\ttokenizer \"github.com/qhenkart/anthropic-tokenizer-go\"\n)\n\n// Config holds proxy configuration.\ntype Config struct {\n\tListenAddr string // e.g. \":9099\"\n\tTargetURL string // e.g. \"https://api.anthropic.com\"\n\tTokenThreshold int // trigger stubbing above this estimated token count\n\tTokenMinimumThreshold int // stub down to this floor\n\tTokenThresholds map[string]int // model-specific thresholds: {\"opus\": 180000, \"haiku\": 130000}\n\tKeepRecent int // number of messages to always keep unmodified\n\tDataDir string // yesmem data directory for DB access\n\tOpenAITargetURL string // upstream for OpenAI-format clients; if empty, uses TargetURL\n\n\t// Signal reflection\n\tSignalsEnabled bool // enable async signal reflection calls\n\tSignalsMode string // \"reflection\" = separate API call\n\tSignalsEveryNTurns int // every N end_turn responses\n\tSignalsModel string // full model ID, e.g. \"claude-sonnet-4-6\"\n\tAPIKey string // needed for reflection API calls\n\n\t// Sawtooth cache optimization\n\tSawtoothEnabled bool // use sawtooth instead of progressive decay (default: true)\n\tCacheTTL string // \"ephemeral\" (5m) or \"1h\" (extended)\n\n\t// Usage deflation: scale down input_tokens reported to CC to suppress \"Context low\" warning.\n\t// 0 = disabled, 0.7 = report 70% of actual tokens. Real values kept for internal tracking.\n\tUsageDeflationFactor float64\n\n\t// System prompt rewriting\n\tPromptUngate bool // strip \"may or may not be relevant\" disclaimer from CLAUDE.md injection\n\tPromptRewrite bool // strip \"Output efficiency\" + \"short and concise\", inject Ant-quality directives\n\tPromptEnhance bool // CLAUDE.md authority reinforcement, comment discipline, persona-based tone\n\tEffortFloor string // minimum effort level: \"low\", \"medium\", \"high\", \"max\" (empty = off)\n\n\t// Cache keepalive\n\tCacheKeepaliveEnabled bool\n\tCacheKeepaliveMode string // \"auto\", \"5m\", \"1h\"\n\tCacheKeepalivePings5m int\n\tCacheKeepalivePings1h int\n\n\t// Forked agents\n\tForkedAgentsEnabled bool\n\tForkedAgentsModel string // full model ID\n\tForkedAgentsTokenGrowthTrigger int\n\tForkedAgentsMaxFailures int\n\tForkedAgentsMaxForksPerSession int\n\tForkedAgentsDebug bool\n\n\t// Cache state management\n\tResetCache bool // clear persisted frozen stubs and decay state on startup\n}\n\nconst maxAnnotations = 5000 // evict oldest when exceeded\n\n// ANSI color codes for log output\nconst (\n\tcolorReset = \"\\033[0m\"\n\tcolorDim = \"\\033[2m\" // gray — passthrough / no stubbing\n\tcolorGreen = \"\\033[32m\" // stubbed / compressed\n\tcolorLightGreen = \"\\033[92m\" // FINAL without compression\n\tcolorBlue = \"\\033[34m\" // injection\n\tcolorOrange = \"\\033[33m\" // skip / retry\n\tcolorYellow = \"\\033[33m\" // warnings\n\tcolorRed = \"\\033[31m\" // errors\n)\n\n// Server is the infinite-thread proxy.\ntype Server struct {\n\tcfg Config\n\thttpClient *http.Client\n\tlogger *log.Logger\n\trequestIdx atomic.Int64\n\n\t// Post-hoc annotations: tool_use_id → first ~120 chars of Claude's response\n\tmu sync.RWMutex\n\tannotations map[string]string\n\n\t// Progressive decay tracker\n\tdecay *DecayTracker\n\n\t// Living narrative block\n\tnarrative *Narrative\n\n\t// Pivot moments cache (from daemon)\n\tpivotMu sync.RWMutex\n\tpivotTexts []string\n\tpivotCached time.Time\n\n\t// Aggregate stats (Task #2)\n\tstats *ProxyStats\n\n\t// Local tokenizer for accurate token counting\n\ttokenizer *tokenizer.Tokenizer\n\n\t// Cached overhead tokens (system + tools), set on first request per thread\n\toverheadMu sync.RWMutex\n\toverheadTokens int // unused, kept for interface compat\n\n\t// Hysteresis state for stubbing (Task #6)\n\tstubActive atomic.Bool\n\n\t// Daemon connection state — first successful connect flips to true\n\tdaemonReady atomic.Bool\n\n\t// Retry detection (Task #7)\n\tretryMu sync.RWMutex\n\tlastFingerprint string\n\n\t// Session start time for temporal annotation in archive blocks\n\tsessionStartMu sync.RWMutex\n\tsessionStartTime time.Time\n\n\t// Runtime config override (set via MCP set_config tool)\n\tconfigOverrideMu sync.RWMutex\n\ttokenThresholdOverrides map[string]int // model-key → threshold, \"\" = global fallback\n\n\t// Briefing cache — loaded once per thread, injected as user/assistant message pair\n\tbriefingMu sync.RWMutex\n\tbriefingText string\n\tcodeMapText string\n\n\t// Cognitive signal bus — routes _signal_* tool calls to handlers\n\tsignalBus *SignalBus\n\n\t// Inline reflection: track which learning IDs were injected per thread\n\tlastInjectedIDsMu sync.Mutex\n\tlastInjectedIDs map[string]map[int64]string // threadID → id → source (\"briefing\"|\"associative\"|\"fresh\")\n\tsessionInjectCounts map[string]map[int64]int // threadID → learningID → injection count this session\n\tlastTurnInjected map[string]map[int64]bool // threadID → learningIDs injected in previous turn\n\n\t// Self-priming anchors: threadID → last self-prime text\n\tselfPrimeMu sync.RWMutex\n\tselfPrimes map[string]string\n\n\t// Timestamp tracking: threadID → time of last response completion\n\tresponseTsMu sync.RWMutex\n\tresponseTimes map[string]time.Time\n\n\t// Think reminder: per-thread request counter (replaces hook-think file-based counter)\n\tthinkMu sync.Mutex\n\tthinkCounters map[string]int // threadID → request count\n\n\tchannelMu sync.Mutex\n\tchannelInjectCount map[string]int // sessionID → injection turn count\n\n\t// Prompt cache gating — enables cache_control breakpoints when requests are frequent\n\tcacheGate *CacheGate\n\n\t// Sawtooth cache optimization\n\tfrozenStubs *FrozenStubs\n\tsawtoothTrigger *SawtoothTrigger\n\ttimestampStore *TimestampStore\n\tcacheStatusWriter *CacheStatusWriter\n\tcacheKeepalive *CacheKeepalive\n\tcacheTTLDetector *CacheTTLDetector\n\n\t// Cumulative token savings from collapsing (per threadID)\n\trawSavingsMu sync.Mutex\n\trawSavings map[string]int\n\n\t// Skill hint tracking: per-thread active skills\n\tskillTracker *skillHintTracker\n\n\t// Forked agent state: per-thread token growth + failure tracking\n\tforkState *ForkState\n\tforkConfigs []ForkConfig\n\n\t// Rules re-injection: condensed CLAUDE.md rules injected every ~40k tokens\n\trulesMu sync.RWMutex\n\trulesBlock string // cached condensed rules (fetched once from daemon)\n\trulesTokenCount map[string]int // threadID → tokens since last rules injection\n\trulesCollapseInjected map[string]bool // threadID → true if collapse already injected rules (reset by normal inject)\n\tmsgCounters *msgCounters // global per-thread msg counter, persists across collapses\n\n\t// Loop detection: per-thread warning state\n\tloopMu sync.Mutex\n\tloopStates map[string]*LoopState // threadID → state\n}\n\n// Run starts the proxy server and blocks until interrupted.\nfunc Run(cfg Config) error {\n\ts := &Server{\n\t\tcfg: cfg,\n\t\thttpClient: &http.Client{\n\t\t\tTimeout: 10 * time.Minute,\n\t\t\tTransport: &http.Transport{\n\t\t\t\tTLSClientConfig: &tls.Config{},\n\t\t\t\tMaxIdleConns: 10,\n\t\t\t\tIdleConnTimeout: 90 * time.Second,\n\t\t\t\tDisableCompression: true,\n\t\t\t\tResponseHeaderTimeout: 60 * time.Second,\n\t\t\t},\n\t\t},\n\t\tlogger: createLogger(cfg.DataDir),\n\t\tannotations: make(map[string]string),\n\t\tdecay: NewDecayTracker(),\n\t\tnarrative: NewNarrative(),\n\t\tstats: &ProxyStats{startTime: time.Now()},\n\t\tselfPrimes: make(map[string]string),\n\t\tlastInjectedIDs: make(map[string]map[int64]string),\n\t\tsessionInjectCounts: make(map[string]map[int64]int),\n\t\tlastTurnInjected: make(map[string]map[int64]bool),\n\t\tresponseTimes: make(map[string]time.Time),\n\t\tthinkCounters: make(map[string]int),\n\t\tchannelInjectCount: make(map[string]int),\n\t\tcacheGate: NewCacheGate(cacheGapForTTL(cfg.CacheTTL)),\n\t\tfrozenStubs: NewFrozenStubsWithTTL(sawtoothTTLForCacheTTL(cfg.CacheTTL)),\n\t\ttimestampStore: NewTimestampStore(),\n\t\tsawtoothTrigger: NewSawtoothTrigger(cacheGapForTTL(cfg.CacheTTL), cfg.TokenThreshold, cfg.TokenMinimumThreshold),\n\t\tcacheStatusWriter: NewCacheStatusWriter(cfg.DataDir, cfg.CacheTTL, cfg.TokenThreshold, cfg.TokenMinimumThreshold),\n\t\tcacheTTLDetector: NewCacheTTLDetectorWithPersist(cfg.DataDir),\n\t\tskillTracker: newSkillHintTracker(),\n\t\trulesTokenCount: make(map[string]int),\n\t\trulesCollapseInjected: make(map[string]bool),\n\t\tmsgCounters: newMsgCounters(),\n\t\tloopStates: make(map[string]*LoopState),\n\t\tforkState: NewForkState(cfg.ForkedAgentsTokenGrowthTrigger, cfg.ForkedAgentsMaxFailures, cfg.ForkedAgentsMaxForksPerSession),\n\t\tforkConfigs: []ForkConfig{},\n\t}\n\n\t// Log persisted detection state\n\tif sup := s.cacheTTLDetector.Is1hSupported(); sup != nil {\n\t\ts.logger.Printf(\"[proxy] loaded persisted TTL detection: 1h_supported=%v\", *sup)\n\t\tif *sup && s.frozenStubs != nil {\n\t\t\ts.frozenStubs.UpdateTTL(sawtoothTTLForCacheTTL(\"1h\"))\n\t\t}\n\t}\n\n\tif cfg.CacheKeepaliveEnabled {\n\t\ts.cacheKeepalive = NewCacheKeepalive(CacheKeepaliveConfig{\n\t\t\tTarget: cfg.TargetURL,\n\t\t\tMode: cfg.CacheKeepaliveMode,\n\t\t\tPings5m: cfg.CacheKeepalivePings5m,\n\t\t\tPings1h: cfg.CacheKeepalivePings1h,\n\t\t\tDetector: s.cacheTTLDetector,\n\t\t\tLogger: s.logger,\n\t\t\tOnPing: func(threadID string, cacheRead, cacheWrite int) {\n\t\t\t\ts.cacheTTLDetector.RecordResponse(cacheRead, cacheWrite, 0)\n\t\t\t\tif s.frozenStubs != nil {\n\t\t\t\t\ts.frozenStubs.Touch(threadID)\n\t\t\t\t}\n\t\t\t\tif s.sawtoothTrigger != nil {\n\t\t\t\t\ts.sawtoothTrigger.TouchRequestTime(threadID)\n\t\t\t\t}\n\t\t\t},\n\t\t})\n\t}\n\n\t// Wire detector + keepalive to status writer for terminal display\n\ts.cacheStatusWriter.SetDetector(s.cacheTTLDetector)\n\tif s.cacheKeepalive != nil {\n\t\ts.cacheStatusWriter.SetKeepalive(s.cacheKeepalive)\n\t}\n\n\t// Register forked agent configs\n\tif cfg.ForkedAgentsEnabled {\n\t\ts.forkConfigs = append(s.forkConfigs, NewExtractAndEvaluateConfig(cfg.ForkedAgentsModel))\n\t\tmodelDesc := cfg.ForkedAgentsModel\n\t\tif modelDesc == \"\" {\n\t\t\tmodelDesc = \"(same as main thread)\"\n\t\t}\n\t\ts.logger.Printf(\"[proxy] forked agents enabled: %d configs, model=%s, trigger=%dk tokens\",\n\t\t\tlen(s.forkConfigs), modelDesc, cfg.ForkedAgentsTokenGrowthTrigger/1000)\n\t}\n\n\t// Wire sawtooth token persistence via daemon RPC\n\ts.sawtoothTrigger.SetPersistFunc(func(key, value string) {\n\t\tif _, err := s.queryDaemon(\"set_proxy_state\", map[string]any{\"key\": key, \"value\": value}); err != nil {\n\t\t\ts.logger.Printf(\"[sawtooth] persist failed for %s: %v\", key, err)\n\t\t}\n\t})\n\ts.sawtoothTrigger.SetLoadFunc(func(key string) (string, bool) {\n\t\tresult, err := s.queryDaemon(\"get_proxy_state\", map[string]any{\"key\": key})\n\t\tif err != nil || result == nil {\n\t\t\treturn \"\", false\n\t\t}\n\t\tvar resp struct {\n\t\t\tValue string `json:\"value\"`\n\t\t}\n\t\tif json.Unmarshal(result, &resp) != nil || resp.Value == \"\" {\n\t\t\treturn \"\", false\n\t\t}\n\t\treturn resp.Value, true\n\t})\n\n\t// Wire frozen stubs persistence via same daemon RPC\n\ts.frozenStubs.SetPersistFunc(func(key, value string) {\n\t\tif _, err := s.queryDaemon(\"set_proxy_state\", map[string]any{\"key\": key, \"value\": value}); err != nil {\n\t\t\ts.logger.Printf(\"[frozen] persist failed for %s: %v\", key, err)\n\t\t}\n\t})\n\ts.frozenStubs.SetLoadFunc(func(key string) (string, bool) {\n\t\tresult, err := s.queryDaemon(\"get_proxy_state\", map[string]any{\"key\": key})\n\t\tif err != nil || result == nil {\n\t\t\treturn \"\", false\n\t\t}\n\t\tvar resp struct {\n\t\t\tValue string `json:\"value\"`\n\t\t}\n\t\tif json.Unmarshal(result, &resp) != nil || resp.Value == \"\" {\n\t\t\treturn \"\", false\n\t\t}\n\t\treturn resp.Value, true\n\t})\n\n\t// Wire decay tracker persistence via same daemon RPC\n\ts.decay.SetPersistFunc(func(key, value string) {\n\t\tif _, err := s.queryDaemon(\"set_proxy_state\", map[string]any{\"key\": key, \"value\": value}); err != nil {\n\t\t\ts.logger.Printf(\"[decay] persist failed for %s: %v\", key, err)\n\t\t}\n\t})\n\ts.decay.SetLoadFunc(func(key string) (string, bool) {\n\t\tresult, err := s.queryDaemon(\"get_proxy_state\", map[string]any{\"key\": key})\n\t\tif err != nil || result == nil {\n\t\t\treturn \"\", false\n\t\t}\n\t\tvar resp struct {\n\t\t\tValue string `json:\"value\"`\n\t\t}\n\t\tif json.Unmarshal(result, &resp) != nil || resp.Value == \"\" {\n\t\t\treturn \"\", false\n\t\t}\n\t\treturn resp.Value, true\n\t})\n\n\t// Wire timestamp store persistence via same daemon RPC\n\ts.timestampStore.SetPersistFunc(func(key, value string) {\n\t\tif _, err := s.queryDaemon(\"set_proxy_state\", map[string]any{\"key\": key, \"value\": value}); err != nil {\n\t\t\ts.logger.Printf(\"[timestamps] persist failed for %s: %v\", key, err)\n\t\t}\n\t})\n\ts.timestampStore.SetLoadFunc(func(key string) (string, bool) {\n\t\tresult, err := s.queryDaemon(\"get_proxy_state\", map[string]any{\"key\": key})\n\t\tif err != nil || result == nil {\n\t\t\treturn \"\", false\n\t\t}\n\t\tvar resp struct {\n\t\t\tValue string `json:\"value\"`\n\t\t}\n\t\tif json.Unmarshal(result, &resp) != nil || resp.Value == \"\" {\n\t\t\treturn \"\", false\n\t\t}\n\t\treturn resp.Value, true\n\t})\n\n\t// Clear persisted cache state if --reset-cache was passed\n\tif cfg.ResetCache {\n\t\tfor _, prefix := range []string{\"frozen:\", \"decay:\", \"timestamps:\"} {\n\t\t\tresult, err := s.queryDaemon(\"delete_proxy_state_prefix\", map[string]any{\"prefix\": prefix})\n\t\t\tif err != nil {\n\t\t\t\ts.logger.Printf(\"[reset-cache] failed to delete %s*: %v\", prefix, err)\n\t\t\t} else {\n\t\t\t\tvar resp struct{ Deleted int }\n\t\t\t\tjson.Unmarshal(result, &resp)\n\t\t\t\ts.logger.Printf(\"[reset-cache] deleted %d %s* entries\", resp.Deleted, prefix)\n\t\t\t}\n\t\t}\n\t}\n\n\t// Init signal bus with daemon RPC as callback\n\ts.signalBus = NewSignalBus(s.logger)\n\tregisterSignalHandlers(s.signalBus, s.logger, s.queryDaemon, func(threadID, anchor string) {\n\t\ts.selfPrimeMu.Lock()\n\t\ts.selfPrimes[threadID] = anchor\n\t\ts.selfPrimeMu.Unlock()\n\t})\n\n\t// Init local tokenizer (BPE-based, ~60ms)\n\ttok, err := tokenizer.New()\n\tif err != nil {\n\t\ts.logger.Printf(\"%s[proxy] tokenizer init failed, falling back to heuristic: %v%s\", colorOrange, err, colorReset)\n\t} else {\n\t\ts.tokenizer = tok\n\t\ts.logger.Printf(\"[proxy] tokenizer initialized\")\n\t}\n\n\tmux := http.NewServeMux()\n\tmux.HandleFunc(\"/health\", s.handleHealth)\n\tmux.HandleFunc(\"/\", s.handleRequest)\n\n\tsrv := &http.Server{\n\t\tAddr: sanitizeListenAddr(cfg.ListenAddr),\n\t\tHandler: mux,\n\t}\n\n\tdone := make(chan os.Signal, 1)\n\tsignal.Notify(done, syscall.SIGINT, syscall.SIGTERM)\n\n\tgo func() {\n\t\t<-done\n\t\ts.logger.Println(\"shutting down (draining in-flight requests)...\")\n\t\tif s.cacheKeepalive != nil {\n\t\t\ts.cacheKeepalive.Stop()\n\t\t}\n\t\tctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)\n\t\tdefer cancel()\n\t\tif err := srv.Shutdown(ctx); err != nil {\n\t\t\ts.logger.Printf(\"graceful shutdown failed: %v, forcing close\", err)\n\t\t\tsrv.Close()\n\t\t}\n\t}()\n\n\ts.logger.Printf(\"listening on %s → %s (threshold=%d, keepRecent=%d, model_thresholds=%v)\",\n\t\tsanitizeListenAddr(cfg.ListenAddr), cfg.TargetURL, cfg.TokenThreshold, cfg.KeepRecent, cfg.TokenThresholds)\n\n\tif err := srv.ListenAndServe(); err != http.ErrServerClosed {\n\t\treturn fmt.Errorf(\"listen: %w\", err)\n\t}\n\treturn nil\n}\n\n// effectiveTokenThreshold returns the runtime token threshold for a given model.\n// Priority: MCP override (model-specific) > MCP override (global) > config model-specific > config global.\nfunc (s *Server) effectiveTokenThreshold(model string) int {\n\ts.configOverrideMu.RLock()\n\toverrides := s.tokenThresholdOverrides\n\ts.configOverrideMu.RUnlock()\n\tif len(overrides) > 0 && model != \"\" {\n\t\tlower := strings.ToLower(model)\n\t\tfor key, threshold := range overrides {\n\t\t\tif key != \"\" && strings.Contains(lower, strings.ToLower(key)) && threshold > 0 {\n\t\t\t\treturn threshold\n\t\t\t}\n\t\t}\n\t}\n\t// Global override fallback (key \"\")\n\tif len(overrides) > 0 {\n\t\tif globalOverride, ok := overrides[\"\"]; ok && globalOverride > 0 {\n\t\t\treturn globalOverride\n\t\t}\n\t}\n\tif model != \"\" && len(s.cfg.TokenThresholds) > 0 {\n\t\tlower := strings.ToLower(model)\n\t\tfor key, threshold := range s.cfg.TokenThresholds {\n\t\t\tif strings.Contains(lower, strings.ToLower(key)) {\n\t\t\t\treturn threshold\n\t\t\t}\n\t\t}\n\t}\n\treturn s.cfg.TokenThreshold\n}\n\n// SetTokenThresholdOverride sets a runtime override for the token threshold per model key.\n// Pass threshold=0 to reset the override for that key. Key \"\" is the global fallback.\nfunc (s *Server) SetTokenThresholdOverride(modelKey string, threshold int) {\n\ts.configOverrideMu.Lock()\n\tif s.tokenThresholdOverrides == nil {\n\t\ts.tokenThresholdOverrides = make(map[string]int)\n\t}\n\tif threshold <= 0 {\n\t\tdelete(s.tokenThresholdOverrides, modelKey)\n\t} else {\n\t\ts.tokenThresholdOverrides[modelKey] = threshold\n\t}\n\ts.configOverrideMu.Unlock()\n\n\t// Propagate global threshold to sawtooth trigger so runtime overrides take effect\n\tif modelKey == \"\" && s.sawtoothTrigger != nil {\n\t\tif threshold > 0 {\n\t\t\ts.sawtoothTrigger.SetTokenThreshold(threshold)\n\t\t} else {\n\t\t\ts.sawtoothTrigger.SetTokenThreshold(s.cfg.TokenThreshold)\n\t\t}\n\t}\n}\n\n// refreshConfigOverrides loads runtime config overrides from daemon proxy_state.\n// Checks session-specific override first (by threadID), then falls back to global.\nfunc (s *Server) refreshConfigOverrides(threadID string) {\n\t// Try session-specific first\n\tif threadID != \"\" {\n\t\tresult, err := s.queryDaemon(\"get_proxy_state\", map[string]any{\"key\": \"config_override:token_threshold:\" + threadID})\n\t\tif err == nil && result != nil {\n\t\t\tvar resp struct {\n\t\t\t\tValue string `json:\"value\"`\n\t\t\t}\n\t\t\tif json.Unmarshal(result, &resp) == nil && resp.Value != \"\" {\n\t\t\t\tif v, err := strconv.Atoi(resp.Value); err == nil && v > 0 {\n\t\t\t\t\ts.SetTokenThresholdOverride(\"\", v)\n\t\t\t\t\treturn\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\t// Fall back to global\n\tresult, err := s.queryDaemon(\"get_proxy_state\", map[string]any{\"key\": \"config_override:token_threshold\"})\n\tif err != nil || result == nil {\n\t\treturn\n\t}\n\tvar resp struct {\n\t\tValue string `json:\"value\"`\n\t}\n\tif json.Unmarshal(result, &resp) == nil && resp.Value != \"\" {\n\t\tif v, err := strconv.Atoi(resp.Value); err == nil && v > 0 {\n\t\t\ts.SetTokenThresholdOverride(\"\", v)\n\t\t}\n\t}\n}\n\n// queryDaemon opens a fresh unix socket connection per call (thread-safe, no import cycle).\n// ensureFrozenPersisted checks if frozen stubs for a thread are in the DB.\n// If missing (e.g. daemon was unreachable after deploy), re-persists them.\n// Runs asynchronously to avoid blocking the request path.\nfunc (s *Server) ensureFrozenPersisted(threadID string) {\n\tgo func() {\n\t\tkey := \"frozen:\" + threadID\n\t\tresult, err := s.queryDaemon(\"get_proxy_state\", map[string]any{\"key\": key})\n\t\tif err != nil {\n\t\t\treturn // daemon still unreachable, will retry next request\n\t\t}\n\t\tvar resp struct{ Value string }\n\t\tif json.Unmarshal(result, &resp) == nil && resp.Value != \"\" {\n\t\t\treturn // already persisted\n\t\t}\n\t\t// Not in DB — trigger re-persist from in-memory state\n\t\ts.frozenStubs.Persist(threadID)\n\t\ts.decay.Persist(threadID)\n\t\ts.logger.Printf(\"[frozen] re-persisted stubs for %s (was missing from DB)\", threadID)\n\t}()\n}\n\nfunc (s *Server) queryDaemon(method string, params map[string]any) (json.RawMessage, error) {\n\tif s.cfg.DataDir == \"\" {\n\t\treturn nil, fmt.Errorf(\"no data dir configured\")\n\t}\n\tsockPath := filepath.Join(s.cfg.DataDir, \"daemon.sock\")\n\n\t// On cold start (daemon not yet confirmed ready), retry longer to survive daemon restart\n\tmaxAttempts := 3\n\tif !s.daemonReady.Load() {\n\t\tmaxAttempts = 30 // 30 x 500ms = 15s max wait for daemon startup\n\t}\n\n\tvar conn net.Conn\n\tvar err error\n\tfor attempt := 0; attempt < maxAttempts; attempt++ {\n\t\tconn, err = net.DialTimeout(\"unix\", sockPath, 3*time.Second)\n\t\tif err == nil {\n\t\t\ts.daemonReady.Store(true)\n\t\t\tbreak\n\t\t}\n\t\tif attempt < maxAttempts-1 {\n\t\t\ttime.Sleep(500 * time.Millisecond)\n\t\t}\n\t}\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"dial daemon: %w\", err)\n\t}\n\tdefer conn.Close()\n\t// Briefing generation can take 10-20s (LLM refinement + scanner);\n\t// other RPCs (search, learnings) are fast and fine with 5s.\n\tdeadline := 5 * time.Second\n\tif method == \"generate_briefing\" {\n\t\tdeadline = 30 * time.Second\n\t}\n\tconn.SetDeadline(time.Now().Add(deadline))\n\n\t// JSON-RPC style request/response (matches daemon.SocketServer protocol)\n\ttype request struct {\n\t\tMethod string `json:\"method\"`\n\t\tParams map[string]any `json:\"params,omitempty\"`\n\t}\n\ttype response struct {\n\t\tResult json.RawMessage `json:\"result,omitempty\"`\n\t\tError string `json:\"error,omitempty\"`\n\t}\n\n\tif err := json.NewEncoder(conn).Encode(request{Method: method, Params: params}); err != nil {\n\t\treturn nil, fmt.Errorf(\"send: %w\", err)\n\t}\n\n\tvar resp response\n\tif err := json.NewDecoder(conn).Decode(&resp); err != nil {\n\t\treturn nil, fmt.Errorf(\"recv: %w\", err)\n\t}\n\tif resp.Error != \"\" {\n\t\treturn nil, fmt.Errorf(\"daemon: %s\", resp.Error)\n\t}\n\treturn resp.Result, nil\n}\n\nfunc (s *Server) handleRequest(w http.ResponseWriter, r *http.Request) {\n\tif isResponsesPath(r) {\n\t\ts.handleResponses(w, r)\n\t\treturn\n\t}\n\tif isOpenAIPath(r) {\n\t\ts.handleOpenAICompletions(w, r)\n\t\treturn\n\t}\n\tif r.Method == \"POST\" && strings.HasPrefix(r.URL.Path, \"/v1/messages\") &&\n\t\t!strings.Contains(r.URL.Path, \"/batches\") &&\n\t\t!strings.Contains(r.URL.Path, \"/count_tokens\") {\n\t\ts.handleMessages(w, r)\n\t\treturn\n\t}\n\ts.passthrough(w, r)\n}\n\nfunc (s *Server) handleMessages(w http.ResponseWriter, r *http.Request) {\n\treqIdx := int(s.requestIdx.Add(1))\n\n\t// Track session start time (first request sets it)\n\ts.sessionStartMu.Lock()\n\tif s.sessionStartTime.IsZero() {\n\t\ts.sessionStartTime = time.Now()\n\t}\n\ts.sessionStartMu.Unlock()\n\n\t// Sawtooth: periodic TTL eviction of frozen stubs\n\tif s.cfg.SawtoothEnabled && reqIdx%50 == 0 {\n\t\tif n := s.frozenStubs.Evict(); n > 0 {\n\t\t\ts.logger.Printf(\"[req %d] SAWTOOTH: evicted %d stale frozen stubs\", reqIdx, n)\n\t\t}\n\t}\n\n\t// Task #9: Bypass — skip all proxy logic\n\tif isBypassed(r.Header) {\n\t\ts.logger.Printf(\"%s[req %d] bypass active, passthrough%s\", colorDim, reqIdx, colorReset)\n\t\tbody, err := io.ReadAll(r.Body)\n\t\tr.Body.Close()\n\t\tif err != nil {\n\t\t\thttp.Error(w, \"failed to read request body\", http.StatusBadGateway)\n\t\t\treturn\n\t\t}\n\t\ts.forwardRaw(w, r, body)\n\t\treturn\n\t}\n\n\tbody, err := io.ReadAll(r.Body)\n\tr.Body.Close()\n\tif err != nil {\n\t\ts.logger.Printf(\"[req %d] read body error: %v\", reqIdx, err)\n\t\thttp.Error(w, \"failed to read request body\", http.StatusBadGateway)\n\t\treturn\n\t}\n\n\t// Parse JSON\n\tvar req map[string]any\n\tif err := json.Unmarshal(body, &req); err != nil {\n\t\ts.logger.Printf(\"%s[req %d] JSON parse error, passing through: %v%s\", colorDim, reqIdx, err, colorReset)\n\t\ts.forwardRaw(w, r, body)\n\t\treturn\n\t}\n\n\tmessages, ok := req[\"messages\"].([]any)\n\tif !ok {\n\t\ts.forwardRaw(w, r, body)\n\t\treturn\n\t}\n\n\treq[\"messages\"] = messages\n\n\t// Count ALL raw messages BEFORE sawtooth — this gives the correct total msg:N\n\t// for the entire session (CC sends all messages, proxy compacts later).\n\trawMsgCount := len(messages)\n\n\t// Use session_id directly as thread ID (unique per CC session).\n\t// Prefer X-Claude-Code-Session-Id header (CC v2.1.86+), fallback to body metadata.\n\tthreadID := extractSessionID(req, r.Header.Get(\"X-Claude-Code-Session-Id\"))\n\tif threadID == \"\" {\n\t\tthreadID = DeriveThreadID(req)\n\t}\n\tproj := extractProjectName(req)\n\tmodel, _ := req[\"model\"].(string)\n\n\t// Refresh runtime config overrides from daemon (per-session or global)\n\ts.refreshConfigOverrides(threadID)\n\n\t// Track whether req has been modified and needs re-serialization\n\tneedsReserialization := false\n\n\t// Message count at request entry (used for actual-based token estimation)\n\tmsgCount := len(messages)\n\n\t// Log message count + cache breakpoint positions for diagnostics\n\ts.logger.Printf(\"req %d: %d messages total\", reqIdx, msgCount)\n\tlogCacheBreakpointLocations(req, s.logger)\n\n\t// Pre-modification dump: capture the raw request before any proxy changes.\n\tif os.Getenv(\"YESMEM_PROXY_DEBUG\") == \"1\" {\n\t\tif preBody, err := json.Marshal(req); err == nil {\n\t\t\tdumpDir := filepath.Join(s.cfg.DataDir, \"debug\")\n\t\t\tos.MkdirAll(dumpDir, 0755)\n\t\t\tts := time.Now().Format(\"20060102-150405\")\n\t\t\tdumpPath := fmt.Sprintf(\"%s/req_%s_%d_pre.json\", dumpDir, ts, reqIdx)\n\t\t\tos.WriteFile(dumpPath, preBody, 0644)\n\t\t\ts.logger.Printf(\"[req %d] pre-dump %dk to %s\", reqIdx, len(preBody)/1024, dumpPath)\n\t\t}\n\t}\n\n\t// Cache bug mitigations (CC Bun fork issues):\n\t// Bug 1: Normalize cch= sentinel patterns in message content to prevent\n\t// Bun's string replacement from corrupting the wrong occurrence.\n\tif SanitizeBillingSentinel(messages) {\n\t\ts.logger.Printf(\"[req %d] CACHE: sanitized billing sentinel in message content\", reqIdx)\n\t\tneedsReserialization = true\n\t}\n\t// Bug 2: Normalize system[0] billing hash to a fixed value so the cache\n\t// prefix stays stable across fresh/resume session boundaries.\n\tif NormalizeBillingHeader(req) {\n\t\ts.logger.Printf(\"[req %d] CACHE: normalized billing header cch= hash\", reqIdx)\n\t\tneedsReserialization = true\n\t}\n\n\t// prompt_ungate: strip the CLAUDE.md subordination disclaimer so user instructions carry full authority.\n\tif s.cfg.PromptUngate {\n\t\tif StripCLAUDEMDDisclaimer(req) {\n\t\t\ts.logger.Printf(\"[req %d] SYSTEM: stripped CLAUDE.md disclaimer\", reqIdx)\n\t\t\tneedsReserialization = true\n\t\t}\n\t}\n\n\t// prompt_rewrite: strip output-throttling directives, rewrite quality caps, inject Ant-quality directives\n\tif s.cfg.PromptRewrite {\n\t\t// StripOutputEfficiency: disabled — Anthropic replaced # Output efficiency with # Text output (CC ~2.1.117)\n\t\t// if StripOutputEfficiency(req) {\n\t\t// \ts.logger.Printf(\"[req %d] REWRITE: stripped Output efficiency section\", reqIdx)\n\t\t// \tneedsReserialization = true\n\t\t// }\n\t\tif StripToneBrevity(req) {\n\t\t\ts.logger.Printf(\"[req %d] REWRITE: stripped 'short and concise' from Tone\", reqIdx)\n\t\t\tneedsReserialization = true\n\t\t}\n\t\tif RewriteGoldPlating(req) {\n\t\t\ts.logger.Printf(\"[req %d] REWRITE: rewritten gold-plating directive\", reqIdx)\n\t\t\tneedsReserialization = true\n\t\t}\n\t\tif RewriteErrorHandling(req) {\n\t\t\ts.logger.Printf(\"[req %d] REWRITE: rewritten error handling directive\", reqIdx)\n\t\t\tneedsReserialization = true\n\t\t}\n\t\tif RewriteThreeLinesRule(req) {\n\t\t\ts.logger.Printf(\"[req %d] REWRITE: rewritten three-lines rule\", reqIdx)\n\t\t\tneedsReserialization = true\n\t\t}\n\t\tif RewriteSubagentCompleteness(req) {\n\t\t\ts.logger.Printf(\"[req %d] REWRITE: rewritten subagent completeness\", reqIdx)\n\t\t\tneedsReserialization = true\n\t\t}\n\t\tif RewriteExploreAgentSpeed(req) {\n\t\t\ts.logger.Printf(\"[req %d] REWRITE: rewritten explore agent speed bias\", reqIdx)\n\t\t\tneedsReserialization = true\n\t\t}\n\t\tif RewriteSubagentCodeSuppression(req) {\n\t\t\ts.logger.Printf(\"[req %d] REWRITE: rewritten subagent code suppression\", reqIdx)\n\t\t\tneedsReserialization = true\n\t\t}\n\t\tif RewriteScopeMatching(req) {\n\t\t\ts.logger.Printf(\"[req %d] REWRITE: rewritten scope matching\", reqIdx)\n\t\t\tneedsReserialization = true\n\t\t}\n\t\tInjectAntDirectives(req)\n\t\ts.logger.Printf(\"[req %d] REWRITE: injected Ant-quality directives\", reqIdx)\n\t\tneedsReserialization = true\n\t}\n\n\t// prompt_enhance: CLAUDE.md authority + comment discipline + persona tone\n\tif s.cfg.PromptEnhance {\n\t\tInjectCLAUDEMDAuthority(req)\n\t\tif raw, err := s.queryDaemon(\"get_persona\", nil); err == nil {\n\t\t\tvar persona map[string]any\n\t\t\tif json.Unmarshal(raw, &persona) == nil {\n\t\t\t\tif traits, ok := persona[\"traits\"].(map[string]any); ok {\n\t\t\t\t\tif verbosity, ok := traits[\"verbosity\"].(string); ok {\n\t\t\t\t\t\tInjectPersonaTone(req, verbosity)\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\ts.logger.Printf(\"[req %d] ENHANCE: injected authority + persona tone\", reqIdx)\n\t\tneedsReserialization = true\n\t}\n\n\t// thinking normalization: convert thinking.type \"enabled\" → \"adaptive\" for models that require it\n\tif NormalizeThinkingType(req) {\n\t\ts.logger.Printf(\"[req %d] THINKING: normalized type=enabled → adaptive for %s\", reqIdx, model)\n\t\tneedsReserialization = true\n\t}\n\n\t// effort_floor: ensure output_config.effort is at least the configured minimum\n\tif s.cfg.EffortFloor != \"\" {\n\t\tif EnforceEffortFloor(req, s.cfg.EffortFloor) {\n\t\t\ts.logger.Printf(\"[req %d] EFFORT: enforced floor=%s\", reqIdx, s.cfg.EffortFloor)\n\t\t\tneedsReserialization = true\n\t\t}\n\t}\n\n\t// Inject identity hint on first message of a thread so Claude knows the briefing is from itself.\n\tif len(messages) <= 1 {\n\t\tAppendSystemBlock(req, \"yesmem-identity\", \"The first user message contains your briefing — written by yourself, from your memory. This is not an external document. This is you, remembering.\")\n\t\tneedsReserialization = true\n\t}\n\n\t// Token estimation: actual-based (lastActual + delta) or full count fallback\n\toverhead := s.measureOverhead(req)\n\ttotalTokens := s.estimateTotalTokens(threadID, messages, overhead)\n\n\t// Task #7: Retry detection — skip side-effects on retries\n\tfp := requestFingerprint(messages)\n\tisRetryReq := s.isRetry(fp)\n\tif isRetryReq {\n\t\ts.logger.Printf(\"%s[req %d %s tid=%s] retry detected, skipping side-effects%s\", colorOrange, reqIdx, proj, threadID, colorReset)\n\t}\n\n\t// Increment project turn counter (async, non-blocking, skip on retries)\n\tif !isRetryReq {\n\t\tturnProject := proj\n\t\tif turnProject == \"\" {\n\t\t\tturnProject = \"__global__\"\n\t\t}\n\t\tgo s.queryDaemon(\"increment_turn\", map[string]any{\"project\": turnProject})\n\t}\n\n\t// Extract user query once (used by re-expansion and associative retrieval)\n\tuserQuery := lastUserText(messages)\n\n\t// Extract tool_use_ids for post-hoc annotation (passed as parameter, no shared state)\n\ttoolUseIDs := extractToolUseIDs(messages)\n\n\t// === Inline Reflection: scan previous assistant response for signals ===\n\ts.lastInjectedIDsMu.Lock()\n\tprevInjected := s.lastInjectedIDs[threadID]\n\ts.lastInjectedIDsMu.Unlock()\n\n\tif len(prevInjected) > 0 && !isRetryReq {\n\t\tsignals := scanAssistantSignals(messages)\n\n\t\t// Signal 1: Learning Used — use_count (all sources)\n\t\tvar confirmedUsed []int64\n\t\tfor _, id := range signals.UsedIDs {\n\t\t\tif _, ok := prevInjected[id]; ok {\n\t\t\t\tconfirmedUsed = append(confirmedUsed, id)\n\t\t\t}\n\t\t}\n\t\tif len(confirmedUsed) > 0 {\n\t\t\tidsAny := make([]any, len(confirmedUsed))\n\t\t\tfor i, id := range confirmedUsed {\n\t\t\t\tidsAny[i] = id\n\t\t\t}\n\t\t\tgo s.queryDaemon(\"increment_use\", map[string]any{\"ids\": idsAny})\n\t\t}\n\n\t\t// Signal 2: Noise — only for associative and fresh injections.\n\t\t// Briefing learnings are injected every turn, so \"not used this turn\"\n\t\t// is expected and not noise. Only contextual injections that were\n\t\t// ignored are real noise signals.\n\t\tusedSet := make(map[int64]bool, len(confirmedUsed))\n\t\tfor _, id := range confirmedUsed {\n\t\t\tusedSet[id] = true\n\t\t}\n\t\tvar noiseIDs []int64\n\t\tfor id, source := range prevInjected {\n\t\t\tif usedSet[id] {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tif source == \"associative\" || source == \"fresh\" {\n\t\t\t\tnoiseIDs = append(noiseIDs, id)\n\t\t\t}\n\t\t\t// briefing: skip — not being used in one turn is expected\n\t\t}\n\t\tif len(noiseIDs) > 0 {\n\t\t\tidsAny := make([]any, len(noiseIDs))\n\t\t\tfor i, id := range noiseIDs {\n\t\t\t\tidsAny[i] = id\n\t\t\t}\n\t\t\tgo s.queryDaemon(\"increment_noise\", map[string]any{\"ids\": idsAny})\n\t\t}\n\n\t\t// Signal 3: Error Feedback — fail_count on used learnings when tool errors occurred.\n\t\t// If Claude referenced a learning and the resulting action still failed,\n\t\t// the learning didn't prevent the error. Only bump on USED ids (not all injected).\n\t\tif signals.HasToolErrors && len(confirmedUsed) > 0 {\n\t\t\tfailAny := make([]any, len(confirmedUsed))\n\t\t\tfor i, id := range confirmedUsed {\n\t\t\t\tfailAny[i] = id\n\t\t\t}\n\t\t\tgo s.queryDaemon(\"increment_fail\", map[string]any{\"ids\": failAny})\n\t\t\tif s.logger != nil {\n\t\t\t\ts.logger.Printf(\"[inline-reflection] error feedback: %d used learnings → fail_count++\", len(confirmedUsed))\n\t\t\t}\n\t\t}\n\n\t\t// Signal 4: Knowledge Gaps\n\t\tfor _, topic := range signals.GapTopics {\n\t\t\tgo s.queryDaemon(\"track_gap\", map[string]any{\"topic\": topic, \"project\": proj})\n\t\t}\n\n\t\t// Signal 3: Contradictions\n\t\tfor _, desc := range signals.Contradictions {\n\t\t\tgo s.queryDaemon(\"flag_contradiction\", map[string]any{\"description\": desc, \"project\": proj, \"thread_id\": threadID})\n\t\t}\n\n\t\tif len(confirmedUsed) > 0 || len(signals.GapTopics) > 0 || len(signals.Contradictions) > 0 {\n\t\t\ts.logger.Printf(\"[req %d %s tid=%s] inline-reflection: injected=%d used=%d noise=%d gaps=%d contradictions=%d\",\n\t\t\t\treqIdx, proj, threadID, len(prevInjected), len(confirmedUsed), len(noiseIDs), len(signals.GapTopics), len(signals.Contradictions))\n\t\t}\n\t}\n\n\t// Commit detection: check for git commit in tool_result, trigger async stale-learning evaluation\n\tif ci := detectGitCommit(messages); ci != nil {\n\t\tworkdir := extractWorkingDirectory(req)\n\t\ts.logger.Printf(\"[req %d %s tid=%s] git commit detected: %s %s\", reqIdx, proj, threadID, ci.Hash, ci.Message)\n\t\tgo s.queryDaemon(\"invalidate_on_commit\", map[string]any{\n\t\t\t\"hash\": ci.Hash, \"project\": proj, \"workdir\": workdir,\n\t\t})\n\t}\n\n\t// Loop detection: check for repeating tool-call patterns and inject warning\n\tif threadID != \"\" && !isRetryReq {\n\t\ts.loopMu.Lock()\n\t\tloopState := s.loopStates[threadID]\n\t\tif loopState == nil {\n\t\t\tloopState = &LoopState{}\n\t\t\ts.loopStates[threadID] = loopState\n\t\t}\n\t\ts.loopMu.Unlock()\n\n\t\tif warning, level := CheckLoopAndFormat(messages, loopState); warning != \"\" {\n\t\t\tAppendSystemBlock(req, \"yesmem-loop-warning\", warning)\n\t\t\tneedsReserialization = true\n\t\t\ts.logger.Printf(\"%s[req %d %s tid=%s] LOOP: level %d warning injected%s\", colorOrange, reqIdx, proj, threadID, level, colorReset)\n\t\t}\n\t}\n\n\t// Task #4: Subagent detection — skip all proxy logic for short conversations\n\tif isSubagent(messages, req) {\n\t\t// Inject docs hint for subagents if reference docs are indexed\n\t\tif hint := s.getDocsHint(proj); hint != \"\" {\n\t\t\tmodified := injectDocsHintForSubagent(messages, hint)\n\t\t\treq[\"messages\"] = modified\n\t\t\tif newBody, err := json.Marshal(req); err == nil {\n\t\t\t\tbody = newBody\n\t\t\t}\n\t\t\ts.logger.Printf(\"%s[req %d %s tid=%s] %d messages (subagent, docs-hint injected)%s\", colorOrange, reqIdx, proj, threadID, len(messages), colorReset)\n\t\t} else {\n\t\t\ts.logger.Printf(\"%s[req %d %s tid=%s] %d messages (subagent, passthrough)%s\", colorOrange, reqIdx, proj, threadID, len(messages), colorReset)\n\t\t}\n\t\ts.forwardWithAnnotation(w, r, body, reqIdx, toolUseIDs, proj, threadID, msgCount)\n\t\treturn\n\t}\n\n\t// === Sawtooth Cache Optimization ===\n\t// When enabled: use frozen stubs between stub-cycles for cache hits.\n\t// When disabled: fall back to progressive decay on every request above threshold.\n\n\tsawtoothCutoff := 0 // raw index where fresh tail starts (0 = no frozen stubs)\n\tsawtoothStubs := 0 // number of frozen stubs in combined array\n\n\tif s.cfg.SawtoothEnabled {\n\t\t// Restore decay state from DB on cold start (before frozen stubs use it)\n\t\ts.decay.LoadFromDB(threadID)\n\n\t\t// Check for existing frozen stubs first\n\t\tfrozen := s.frozenStubs.Get(threadID, messages)\n\t\tif frozen != nil {\n\t\t\tsawtoothCutoff = frozen.Cutoff\n\t\t\tsawtoothStubs = len(frozen.Messages)\n\t\t\t// Estimate combined token count: frozen prefix + fresh tail + overhead\n\t\t\tfreshMessages := messages[frozen.Cutoff:]\n\t\t\tfreshTokens := s.countMessageTokens(freshMessages)\n\t\t\tcombinedTokens := frozen.Tokens + freshTokens + overhead\n\n\t\t\tif combinedTokens > s.effectiveTokenThreshold(model) ||\n\t\t\t\ts.sawtoothTrigger.GetLastTokens(threadID) > s.effectiveTokenThreshold(model) {\n\t\t\t\t// Fresh tail grew too large — invalidate and re-stub\n\t\t\t\ts.logger.Printf(\"[req %d %s tid=%s] SAWTOOTH: frozen prefix expired (%dk frozen + %dk fresh + %dk overhead = %dk > %dk threshold)\",\n\t\t\t\t\treqIdx, proj, threadID, frozen.Tokens/1000, freshTokens/1000, overhead/1000, combinedTokens/1000, s.effectiveTokenThreshold(model)/1000)\n\t\t\t\ts.frozenStubs.Invalidate(threadID)\n\t\t\t\tfrozen = nil // fall through to trigger check below\n\t\t\t} else {\n\t\t\t\t// Use frozen prefix + fresh tail\n\t\t\t\tcombined := make([]any, 0, len(frozen.Messages)+len(freshMessages))\n\t\t\t\tcombined = append(combined, frozen.Messages...)\n\t\t\t\tcombined = append(combined, freshMessages...)\n\t\t\t\t// Eager-stub large tool_results in fresh tail (model already processed them)\n\t\t\t\tbeforeEager := s.countMessageTokens(combined[len(frozen.Messages):])\n\t\t\t\tcombined = EagerStubToolResults(combined, len(frozen.Messages), s.countTokens)\n\t\t\t\tafterEager := s.countMessageTokens(combined[len(frozen.Messages):])\n\t\t\t\tif beforeEager != afterEager {\n\t\t\t\t\ts.logger.Printf(\"[req %d %s tid=%s] EAGER-STUB: fresh %dk → %dk (saved %dk)\", reqIdx, proj, threadID, beforeEager/1000, afterEager/1000, (beforeEager-afterEager)/1000)\n\t\t\t\t}\n\t\t\t\treq[\"messages\"] = combined\n\t\t\t\tneedsReserialization = true\n\t\t\t\t// Strip embedded CC breakpoints from frozen stubs — they waste cache slots.\n\t\t\t\t// InjectFrozenStubCacheBreakpoint adds exactly one below.\n\t\t\t\tif n := StripMessagesCacheControl(req, 0, len(frozen.Messages)); n > 0 {\n\t\t\t\t\ts.logger.Printf(\"[req %d %s tid=%s] SAWTOOTH: stripped %d embedded breakpoints from frozen stubs\", reqIdx, proj, threadID, n)\n\t\t\t\t}\n\t\t\t\ts.logger.Printf(\"%s[req %d %s tid=%s] SAWTOOTH: frozen prefix (%d stubs, %dk msg) + %d fresh (%dk msg) + %dk overhead = %dk total%s\",\n\t\t\t\t\tcolorGreen, reqIdx, proj, threadID, len(frozen.Messages), frozen.Tokens/1000, len(freshMessages), freshTokens/1000, overhead/1000, combinedTokens/1000, colorReset)\n\t\t\t\trawMsgTokens := s.countMessageTokens(messages)\n\t\t\t\ts.setRawEstimate(threadID, rawMsgTokens+overhead)\n\t\t\t\ts.logger.Printf(\"[req %d %s tid=%s] RAW ESTIMATE: msgTokens=%dk + overhead=%dk = %dk (messages=%d)\", reqIdx, proj, threadID, rawMsgTokens/1000, overhead/1000, (rawMsgTokens+overhead)/1000, len(messages))\n\t\t\t\tif InjectFrozenStubCacheBreakpoint(req, len(frozen.Messages)) {\n\t\t\t\t\ts.logger.Printf(\"[req %d %s tid=%s] SAWTOOTH: frozen stub cache breakpoint at messages[%d]\",\n\t\t\t\t\t\treqIdx, proj, threadID, len(frozen.Messages)-1)\n\t\t\t\t}\n\n\t\t\t\t// Ensure frozen stubs are persisted (may have failed on first request after deploy)\n\t\t\t\ts.ensureFrozenPersisted(threadID)\n\t\t\t}\n\t\t}\n\t\tif frozen == nil {\n\t\t\t// No frozen stubs — check trigger\n\t\t\ttriggerReason := s.sawtoothTrigger.ShouldTrigger(threadID, totalTokens)\n\t\t\tif triggerReason != TriggerNone {\n\t\t\t\ts.logger.Printf(\"[req %d %s tid=%s] SAWTOOTH stub-cycle triggered: %s (tokens=%dk)\",\n\t\t\t\t\treqIdx, proj, threadID, triggerReason, totalTokens/1000)\n\n\t\t\t\t// Refresh briefing during stub-cycle — disabled (briefing now via SessionStart hook)\n\t\t\t\t// s.refreshBriefing(proj)\n\t\t\t\t// if s.briefingText != \"\" {\n\t\t\t\t// \tUpsertSystemBlockCached(req, \"yesmem-briefing\", s.briefingText)\n\t\t\t\t// }\n\n\t\t\t\t// Run full stub pipeline\n\t\t\t\ts.setRawEstimate(threadID, totalTokens)\n\t\t\t\t_ = s.runStubCycle(messages, req, reqIdx, proj, threadID, overhead, totalTokens, userQuery, isRetryReq)\n\n\t\t\t\t// Freeze the result\n\t\t\t\tfinalMessages, _ := req[\"messages\"].([]any)\n\t\t\t\tif finalMessages != nil && len(finalMessages) > 0 {\n\t\t\t\t\t// Boundary = last message of original that maps to last stub\n\t\t\t\t\tcutoff := len(messages)\n\t\t\t\t\tsawtoothCutoff = cutoff\n\t\t\t\t\tvar boundaryMsg any\n\t\t\t\t\tif cutoff > 0 {\n\t\t\t\t\t\tboundaryMsg = messages[cutoff-1]\n\t\t\t\t\t}\n\t\t\t\t\tfrozenTokens := s.countMessageTokens(finalMessages)\n\t\t\t\t\ts.frozenStubs.Store(threadID, finalMessages, cutoff, boundaryMsg, frozenTokens, totalTokens)\n\t\t\t\t\ts.decay.Persist(threadID)\n\t\t\t\t\ts.logger.Printf(\"[req %d %s tid=%s] SAWTOOTH: frozen %d messages at cutoff=%d (~%dk tokens)\",\n\t\t\t\t\t\treqIdx, proj, threadID, len(finalMessages), cutoff, frozenTokens/1000)\n\t\t\t\t\tif InjectFrozenStubCacheBreakpoint(req, len(finalMessages)) {\n\t\t\t\t\t\ts.logger.Printf(\"[req %d %s tid=%s] SAWTOOTH: frozen stub cache breakpoint at messages[%d]\",\n\t\t\t\t\t\t\treqIdx, proj, threadID, len(finalMessages)-1)\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tneedsReserialization = true\n\t\t\t} else {\n\t\t\t\t// No trigger, no frozen stubs — eager-stub to delay first collapse\n\t\t\t\tbeforeEager := s.countMessageTokens(messages)\n\t\t\t\tmessages = EagerStubToolResults(messages, 0, s.countTokens)\n\t\t\t\tafterEager := s.countMessageTokens(messages)\n\t\t\t\tif beforeEager != afterEager {\n\t\t\t\t\ts.logger.Printf(\"[req %d %s tid=%s] EAGER-STUB: %dk → %dk (saved %dk)\", reqIdx, proj, threadID, beforeEager/1000, afterEager/1000, (beforeEager-afterEager)/1000)\n\t\t\t\t\treq[\"messages\"] = messages\n\t\t\t\t\tneedsReserialization = true\n\t\t\t\t}\n\t\t\t\ts.logger.Printf(\"%s[req %d %s tid=%s] %d messages, ~%dk tokens (sawtooth: no trigger)%s\",\n\t\t\t\t\tcolorDim, reqIdx, proj, threadID, len(messages), totalTokens/1000, colorReset)\n\t\t\t}\n\t\t}\n\t} else {\n\t\t// Legacy path: progressive decay\n\t\tif s.shouldStub(totalTokens, model) {\n\t\t\ts.setRawEstimate(threadID, totalTokens)\n\t\t\t_ = s.runStubCycle(messages, req, reqIdx, proj, threadID, overhead, totalTokens, userQuery, isRetryReq)\n\t\t\tneedsReserialization = true\n\t\t}\n\t}\n\n\t// Persistent timestamp injection: re-injects stored timestamps on ALL previous user messages.\n\t// msg:N is computed from position (counting user messages in raw array), NOT from a counter.\n\t// The injected data is deterministic (same bytes every turn) = cache-safe after initial miss.\n\tif threadID != \"\" {\n\t\tif currentMsgs, ok := req[\"messages\"].([]any); ok && len(currentMsgs) > 0 {\n\t\t\ts.timestampStore.Load(threadID)\n\n\t\t\t// Offset = cutoff - stubsCount: maps combined array index to raw position.\n\t\t\t// Raw position of message at combined index I = cutoff + (I - stubs).\n\t\t\t// msgN = rawPos + 1 = cutoff + (I - stubs) + 1 = (cutoff - stubs) + I + 1.\n\t\t\tmsgOffset := sawtoothCutoff - sawtoothStubs\n\n\t\t\ts.logger.Printf(\"[req %d %s] timestamps: rawTotal=%d, postSawtooth=%d, cutoff=%d, stubs=%d, offset=%d\", reqIdx, proj, rawMsgCount, len(currentMsgs), sawtoothCutoff, sawtoothStubs, msgOffset)\n\n\t\t\t// Pre-store assistant timestamp BEFORE inject loop so it's available immediately\n\t\t\ts.responseTsMu.RLock()\n\t\t\trespTime, hasRespTime := s.responseTimes[threadID]\n\t\t\ts.responseTsMu.RUnlock()\n\t\t\tif hasRespTime {\n\t\t\t\trespStr := shortWeekday(respTime.Weekday()) + \" \" + respTime.Format(\"2006-01-02 15:04:05\")\n\t\t\t\tfor j := len(currentMsgs) - 2; j >= 0; j-- {\n\t\t\t\t\tprevMsg, _ := currentMsgs[j].(map[string]any)\n\t\t\t\t\tif prevMsg == nil || prevMsg[\"role\"] != \"assistant\" {\n\t\t\t\t\t\tcontinue\n\t\t\t\t\t}\n\t\t\t\t\tif !msgHasTextBlock(prevMsg) {\n\t\t\t\t\t\ts.logger.Printf(\"[req %d %s] timestamps: skip assistant[%d] no text block\", reqIdx, proj, j)\n\t\t\t\t\t\tcontinue // skip tool-use-only assistant messages\n\t\t\t\t\t}\n\t\t\t\t\tprevMsgN := msgOffset + j + 1\n\t\t\t\t\tif _, exists := s.timestampStore.Get(threadID, prevMsgN); !exists {\n\t\t\t\t\t\ts.timestampStore.Store(threadID, prevMsgN, &TimestampMeta{Timestamp: respStr})\n\t\t\t\t\t\ts.logger.Printf(\"[req %d %s] timestamps: stored assistant msg:%d ts=%s\", reqIdx, proj, prevMsgN, respStr)\n\t\t\t\t\t} else {\n\t\t\t\t\t\ts.logger.Printf(\"[req %d %s] timestamps: assistant msg:%d already has ts\", reqIdx, proj, prevMsgN)\n\t\t\t\t\t}\n\t\t\t\t\tbreak\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\ts.logger.Printf(\"[req %d %s] timestamps: no responseTimes for thread\", reqIdx, proj)\n\t\t\t}\n\n\t\t\t// Inject [msg:N] (+ timestamp/delta if stored) on all PREVIOUS messages\n\t\t\tif n := InjectTimestamps(s.timestampStore, threadID, currentMsgs, len(currentMsgs)-1, msgOffset, sawtoothStubs); n > 0 {\n\t\t\t\tneedsReserialization = true\n\t\t\t\ts.logger.Printf(\"[req %d %s] timestamps: injected %d\", reqIdx, proj, n)\n\t\t\t}\n\n\t\t\t// Current (last) user message: full timestamp + delta + msg:N from raw count\n\t\t\tlastMsg, _ := currentMsgs[len(currentMsgs)-1].(map[string]any)\n\t\t\tif lastMsg != nil && lastMsg[\"role\"] == \"user\" && !msgHasMetaPrefix(lastMsg) {\n\t\t\t\tnow := time.Now()\n\t\t\t\tnowStr := shortWeekday(now.Weekday()) + \" \" + now.Format(\"2006-01-02 15:04:05\")\n\t\t\t\tentry := &TimestampMeta{Timestamp: nowStr}\n\t\t\t\tif hasRespTime {\n\t\t\t\t\tentry.Delta = formatDelta(now.Sub(respTime))\n\t\t\t\t}\n\t\t\t\ts.timestampStore.Store(threadID, rawMsgCount, entry)\n\t\t\t\tprependMeta(lastMsg, BuildMeta(rawMsgCount, entry))\n\t\t\t\tneedsReserialization = true\n\t\t\t}\n\n\t\t\ts.timestampStore.Persist(threadID)\n\t\t}\n\t}\n\n\t// Associative context injection (for ALL requests, not just stubbed ones)\n\t// Re-enabled with higher thresholds (55/75, max 1) after disabling Fresh Remember.\n\tassocContext := s.findAssociativeContextFor(userQuery, proj, threadID, messages)\n\tif assocContext != \"\" {\n\t\tcurrentMessages, _ := req[\"messages\"].([]any)\n\t\tif currentMessages == nil {\n\t\t\tcurrentMessages = messages\n\t\t}\n\t\treq[\"messages\"] = injectAssociativeContext(currentMessages, assocContext, s.cfg.SawtoothEnabled)\n\t\tneedsReserialization = true\n\t\ts.logger.Printf(\"%s[req %d %s tid=%s] associative context injected%s\", colorBlue, reqIdx, proj, threadID, colorReset)\n\t}\n\n\t// Doc-chunk context injection: separate search path for indexed docs (not skills)\n\tdocContext := s.findDocContextFor(userQuery, proj, messages)\n\tif docContext != \"\" {\n\t\tcurrentMessages, _ := req[\"messages\"].([]any)\n\t\tif currentMessages == nil {\n\t\t\tcurrentMessages = messages\n\t\t}\n\t\treq[\"messages\"] = injectAssociativeContext(currentMessages, docContext, s.cfg.SawtoothEnabled)\n\t\tneedsReserialization = true\n\t\ts.logger.Printf(\"%s[req %d %s tid=%s] doc context injected%s\", colorBlue, reqIdx, proj, threadID, colorReset)\n\t}\n\n\t// Fresh Remember Injection: DISABLED\n\t// Causes echo-loop: Claude saves via remember(), sees it again next turn as\n\t// [yesmem fresh memory], reacts to own output instead of user's request.\n\t// Recovery after /clear is handled by recovery.go (briefing), not here.\n\t// Subagents get their own briefing. No remaining use-case.\n\t// remItems := s.popRecentRemember()\n\tvar remItems []recentLearningItem\n\n\t// Rules re-injection: inject condensed CLAUDE.md rules every ~40k output tokens\n\tif rulesBlock := s.rulesInject(threadID, totalTokens, proj); rulesBlock != \"\" {\n\t\tcurrentMessages, _ := req[\"messages\"].([]any)\n\t\tif currentMessages == nil {\n\t\t\tcurrentMessages = messages\n\t\t}\n\t\treq[\"messages\"] = injectAssociativeContext(currentMessages, s.formatRulesReminder(rulesBlock, proj), s.cfg.SawtoothEnabled)\n\t\tneedsReserialization = true\n\t\ts.logger.Printf(\"%s[req %d %s tid=%s] rules reminder injected%s\", colorBlue, reqIdx, proj, threadID, colorReset)\n\t}\n\n\t// Plan auto-activation: nudge Claude to call set_plan() when reading a plan file\n\tif planFile := detectPlanFileRead(messages); planFile != \"\" {\n\t\tif activePlan, _ := s.getActivePlan(threadID); shouldNudgePlan(planFile, activePlan) {\n\t\t\tAppendSystemBlock(req, \"plan-nudge\",\n\t\t\t\tfmt.Sprintf(\"Du hast einen Implementierungsplan gelesen (%s). Aktiviere ihn jetzt via set_plan() damit Plan-Checkpoints funktionieren und dein Fortschritt getrackt wird.\", filepath.Base(planFile)))\n\t\t\tneedsReserialization = true\n\t\t\ts.logger.Printf(\"%s[req %d %s tid=%s] plan nudge injected for %s%s\", colorBlue, reqIdx, proj, threadID, filepath.Base(planFile), colorReset)\n\t\t}\n\t}\n\n\t// Plan checkpoint: inject plan reminder every ~20k tokens if active plan exists\n\ts.detectPlanToolCall(messages, threadID, totalTokens)\n\tif checkpoint := s.planCheckpointInject(threadID, totalTokens); checkpoint != \"\" {\n\t\tcurrentMessages, _ := req[\"messages\"].([]any)\n\t\tif currentMessages == nil {\n\t\t\tcurrentMessages = messages\n\t\t}\n\t\treq[\"messages\"] = injectAssociativeContext(currentMessages, checkpoint, s.cfg.SawtoothEnabled)\n\t\tneedsReserialization = true\n\t\ts.logger.Printf(\"%s[req %d %s tid=%s] plan checkpoint injected%s\", colorBlue, reqIdx, proj, threadID, colorReset)\n\t}\n\n\t// Docs hint injection: inject docs-available reminder every ~10k tokens (independent of plan)\n\tif docsHint := s.docsHintInject(threadID, totalTokens, proj); docsHint != \"\" {\n\t\tcurrentMessages, _ := req[\"messages\"].([]any)\n\t\tif currentMessages == nil {\n\t\t\tcurrentMessages = messages\n\t\t}\n\t\treq[\"messages\"] = injectAssociativeContext(currentMessages, docsHint, s.cfg.SawtoothEnabled)\n\t\tneedsReserialization = true\n\t\ts.logger.Printf(\"%s[req %d %s tid=%s] docs hint injected%s\", colorBlue, reqIdx, proj, threadID, colorReset)\n\t}\n\tif len(remItems) > 0 {\n\t\tvar lines []string\n\t\tfor _, item := range remItems {\n\t\t\tif item.ID > 0 {\n\t\t\t\tlines = append(lines, fmt.Sprintf(\"- [ID:%d] %s\", item.ID, truncateStr(item.Text, 200)))\n\t\t\t} else {\n\t\t\t\tlines = append(lines, \"- \"+truncateStr(item.Text, 200))\n\t\t\t}\n\t\t}\n\t\tremContext := fmt.Sprintf(\"[yesmem fresh memory]\\nGerade gelernt:\\n%s\\n%s\\n[/yesmem fresh memory]\", strings.Join(lines, \"\\n\"), InlineReflectionHint)\n\t\tcurrentMessages, _ := req[\"messages\"].([]any)\n\t\tif currentMessages == nil {\n\t\t\tcurrentMessages = messages\n\t\t}\n\t\treq[\"messages\"] = injectAssociativeContext(currentMessages, remContext, s.cfg.SawtoothEnabled)\n\t\tneedsReserialization = true\n\t\ts.logger.Printf(\"%s[req %d %s tid=%s] fresh remember injected (%d items)%s\", colorBlue, reqIdx, proj, threadID, len(remItems), colorReset)\n\t}\n\n\t// Skill eval injection: mandatory evaluation instruction for real user input only\n\tif threadID != \"\" && proj != \"\" && isUserInputTurn(messages) {\n\t\t// First: detect any skill activations from message history (updates tracker)\n\t\ts.syncSkillActivations(messages, proj, threadID)\n\n\t\t// Inject mandatory skill evaluation block into last user message\n\t\tskillEval := buildSkillEvalBlock()\n\t\tcurrentMessages, _ := req[\"messages\"].([]any)\n\t\tif currentMessages == nil {\n\t\t\tcurrentMessages = messages\n\t\t}\n\t\treq[\"messages\"] = injectAssociativeContext(currentMessages, skillEval, s.cfg.SawtoothEnabled)\n\t\tneedsReserialization = true\n\t\ts.logger.Printf(\"%s[req %d %s tid=%s] skill-eval injected%s\", colorBlue, reqIdx, proj, threadID, colorReset)\n\t}\n\n\t// === Collect injected learning IDs for next turn's inline reflection ===\n\tif threadID != \"\" {\n\t\tcurrentIDs := make(map[int64]string) // id → source\n\t\t// Source 1: Briefing learnings (cached, same every turn)\n\t\ts.briefingMu.RLock()\n\t\tbriefingSnapshot := s.briefingText\n\t\ts.briefingMu.RUnlock()\n\t\tif briefingSnapshot != \"\" {\n\t\t\tfor _, m := range idPattern.FindAllStringSubmatch(briefingSnapshot, -1) {\n\t\t\t\tif id, err := strconv.ParseInt(m[1], 10, 64); err == nil {\n\t\t\t\t\tcurrentIDs[id] = \"briefing\"\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\t// Source 2: Associative context (contextual, per-turn)\n\t\tif assocContext != \"\" {\n\t\t\tfor _, m := range idPattern.FindAllStringSubmatch(assocContext, -1) {\n\t\t\t\tif id, err := strconv.ParseInt(m[1], 10, 64); err == nil {\n\t\t\t\t\tcurrentIDs[id] = \"associative\" // overwrites briefing if both — associative is the stronger signal\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\t// Source 3: Fresh remember items (just learned)\n\t\tfor _, item := range remItems {\n\t\t\tif item.ID > 0 {\n\t\t\t\tcurrentIDs[item.ID] = \"fresh\"\n\t\t\t}\n\t\t}\n\t\ts.lastInjectedIDsMu.Lock()\n\t\tif len(currentIDs) > 0 {\n\t\t\t// Accumulate IDs across turns — don't overwrite.\n\t\t\t// Claude may reference an ID from turn 30 in turn 50.\n\t\t\texisting := s.lastInjectedIDs[threadID]\n\t\t\tif existing == nil {\n\t\t\t\texisting = make(map[int64]string)\n\t\t\t}\n\t\t\tfor id, source := range currentIDs {\n\t\t\t\texisting[id] = source\n\t\t\t}\n\t\t\ts.lastInjectedIDs[threadID] = existing\n\t\t}\n\t\ts.lastInjectedIDsMu.Unlock()\n\t}\n\n\t// Think reminder injection — only for real user turns, not tool continuations\n\tif threadID != \"\" {\n\t\tcurrentMessages, _ := req[\"messages\"].([]any)\n\t\tif currentMessages == nil {\n\t\t\tcurrentMessages = messages\n\t\t}\n\t\tif lastUserHasText(currentMessages) {\n\t\t\tthinkReminder := s.buildThinkReminder(threadID, threadID, false)\n\t\t\tif thinkReminder != \"\" {\n\t\t\t\treq[\"messages\"] = injectAssociativeContext(currentMessages, thinkReminder, s.cfg.SawtoothEnabled)\n\t\t\t\tneedsReserialization = true\n\t\t\t}\n\t\t}\n\n\t\t// Briefing injection: prepend user/assistant turn pair at beginning of messages.\n\t\t// Static per session → stable prefix → cacheable. Must be before dialog injection.\n\t\tif s.injectBriefingTurn(req, reqIdx, proj, threadID) {\n\t\t\tneedsReserialization = true\n\t\t}\n\n\t\t// Code Map injection: insert after briefing turn pair.\n\t\tif s.injectCodeMapTurn(req, reqIdx, proj, threadID) {\n\t\t\tneedsReserialization = true\n\t\t}\n\n\t\t// Dialog injection: append at end of messages with pseudo-padding for alternation.\n\t\t// Always works regardless of tool_result chains or conversation state.\n\t\tdr := s.checkDialogMessages(threadID, proj)\n\t\tif dr.Extra != \"\" {\n\t\t\t// Prepend session ID for agent-to-agent communication\n\t\t\tdr.Extra = fmt.Sprintf(\"DEINE_SESSION_ID: %s\\n\", threadID) + dr.Extra\n\t\t\tinjected := false\n\t\t\tif msgs, ok := req[\"messages\"].([]any); ok && len(msgs) > 0 {\n\t\t\t\t// Pad with empty assistant if last message is user (maintains alternation)\n\t\t\t\tlastMsg, _ := msgs[len(msgs)-1].(map[string]any)\n\t\t\t\tif lastMsg != nil && lastMsg[\"role\"] == \"user\" {\n\t\t\t\t\tmsgs = append(msgs, map[string]any{\n\t\t\t\t\t\t\"role\": \"assistant\",\n\t\t\t\t\t\t\"content\": \"​\", // zero-width space — invisible padding\n\t\t\t\t\t})\n\t\t\t\t}\n\t\t\t\t// Append dialog as new user message (becomes the last message Claude sees)\n\t\t\t\tmsgs = append(msgs, map[string]any{\n\t\t\t\t\t\"role\": \"user\",\n\t\t\t\t\t\"content\": \"\\n\" + dr.Extra + \"\\n\",\n\t\t\t\t})\n\t\t\t\treq[\"messages\"] = msgs\n\t\t\t\tneedsReserialization = true\n\t\t\t\tinjected = true\n\t\t\t}\n\t\t\t// Mark as read after 3 injection turns — long enough for Claude to process,\n\t\t\t// resilient against fast ping-pong sessions (time-based was broken for <30s turns).\n\t\t\tif injected && dr.HasUnread && dr.SessionID != \"\" {\n\t\t\t\tif s.shouldMarkChannelRead(dr) {\n\t\t\t\t\ts.markDialogRead(dr)\n\t\t\t\t}\n\t\t\t}\n\t\t\ts.logger.Printf(\"[dialog] injection injected=%v extra_len=%d\", injected, len(dr.Extra))\n\t\t}\n\t}\n\n\t// Signal tools are NOT injected into the main request.\n\t// They are collected via a separate async reflection call after the response.\n\t// See fireReflectionCall() in reflection.go.\n\t// NEVER put _signal_* tools back here — the model ignores them among 44+ real tools.\n\n\t// Legacy assistant annotation: timestamp on last assistant message after cache breakpoint.\n\t// User timestamps are now handled by TimestampStore (injected earlier in the pipeline).\n\tif threadID != \"\" {\n\t\tif msgs, ok := req[\"messages\"].([]any); ok && len(msgs) > 0 {\n\t\t\t// Find last cache_control breakpoint in messages\n\t\t\tcacheIdx := -1\n\t\t\tfor i, m := range msgs {\n\t\t\t\tmsg, _ := m.(map[string]any)\n\t\t\t\tif msg == nil {\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\t\t\t\tif _, has := msg[\"cache_control\"]; has {\n\t\t\t\t\tcacheIdx = i\n\t\t\t\t}\n\t\t\t\tif content, ok := msg[\"content\"].([]any); ok {\n\t\t\t\t\tfor _, block := range content {\n\t\t\t\t\t\tif b, ok := block.(map[string]any); ok {\n\t\t\t\t\t\t\tif _, has := b[\"cache_control\"]; has {\n\t\t\t\t\t\t\t\tcacheIdx = i\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t\tstartIdx := cacheIdx + 1\n\t\t\tif startIdx < 0 {\n\t\t\t\tstartIdx = 0\n\t\t\t}\n\t\t\ts.responseTsMu.RLock()\n\t\t\trespTime, hasRespTime := s.responseTimes[threadID]\n\t\t\ts.responseTsMu.RUnlock()\n\t\t\tif hasRespTime {\n\t\t\t\tfor i := len(msgs) - 1; i >= startIdx; i-- {\n\t\t\t\t\tmsg, _ := msgs[i].(map[string]any)\n\t\t\t\t\tif msg != nil && msg[\"role\"] == \"assistant\" {\n\t\t\t\t\t\tmeta := fmt.Sprintf(\"[%s] [msg:%d]\", respTime.Format(\"2006-01-02 15:04:05\"), i)\n\t\t\t\t\t\tprependMeta(msg, meta)\n\t\t\t\t\t\tneedsReserialization = true\n\t\t\t\t\t\tbreak\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\t// Self-Priming: DISABLED (experimental, see signal_handlers.go comment block).\n\t// The reflection-based selfprime (Haiku) was too inaccurate to be useful —\n\t// the main model adapts to user context organically from message history.\n\t// Kept for reference; re-enable by uncommenting below + setting ShouldActivate=true.\n\t//\n\t// s.selfPrimeMu.RLock()\n\t// anchor := s.selfPrimes[threadID]\n\t// s.selfPrimeMu.RUnlock()\n\t// if anchor != \"\" {\n\t// \tif msgs, ok := req[\"messages\"].([]any); ok && len(msgs) > 0 {\n\t// \t\tlastMsg, _ := msgs[len(msgs)-1].(map[string]any)\n\t// \t\tif lastMsg != nil && lastMsg[\"role\"] == \"user\" {\n\t// \t\t\tprimeBlock := map[string]any{\n\t// \t\t\t\t\"type\": \"text\",\n\t// \t\t\t\t\"text\": \"[self-prime]\\n[Self-Prime von deiner letzten Antwort]: \" + anchor,\n\t// \t\t\t}\n\t// \t\t\tif content, ok := lastMsg[\"content\"].([]any); ok {\n\t// \t\t\t\tlastMsg[\"content\"] = append(content, primeBlock)\n\t// \t\t\t} else if text, ok := lastMsg[\"content\"].(string); ok {\n\t// \t\t\t\tlastMsg[\"content\"] = []any{\n\t// \t\t\t\t\tmap[string]any{\"type\": \"text\", \"text\": text},\n\t// \t\t\t\t\tprimeBlock,\n\t// \t\t\t\t}\n\t// \t\t\t}\n\t// \t\t\tneedsReserialization = true\n\t// \t\t}\n\t// \t}\n\t// }\n\n\t// Prompt Caching: inject cache_control breakpoints on system + tools\n\t// Sawtooth: disabled (all 4 slots are used by CC system (2) + briefing (1) + CC messages (1))\n\t// Legacy: count existing breakpoints and fill up remaining slots.\n\tif !s.cfg.SawtoothEnabled && s.cacheGate.ShouldCache() {\n\t\tif n := InjectCacheBreakpoints(req, s.logger); n > 0 {\n\t\t\tneedsReserialization = true\n\t\t\ts.logger.Printf(\"[req %d %s] prompt cache: %d breakpoints injected\", reqIdx, proj, n)\n\t\t} else {\n\t\t\ts.logger.Printf(\"[req %d %s] prompt cache: no budget (4/4 already set)\", reqIdx, proj)\n\t\t}\n\t}\n\n\t// Final: upgrade cache TTL + enforce breakpoint limit (must run AFTER all injections + InjectCacheBreakpoints)\n\t// Shift the last messages-breakpoint from unstable user message to stable assistant message.\n\t// Text user messages change between turns (timestamps, reminders, structure); assistant messages don't.\n\t// Tool-result messages are excluded — they are stable cache anchors.\n\tif ShiftMessageBreakpoint(req) {\n\t\tneedsReserialization = true\n\t}\n\t// TTL upgrade only for API key auth — subscription (OAuth) users should not get extended TTL.\n\tif s.cfg.CacheTTL != \"\" && s.cfg.CacheTTL != \"ephemeral\" && IsAPIKeyAuth(r.Header) {\n\t\tif n := UpgradeCacheTTL(req, s.cfg.CacheTTL); n > 0 {\n\t\t\tneedsReserialization = true\n\t\t\ts.logger.Printf(\"[req %d %s] cache TTL upgraded: %d blocks → %s\", reqIdx, proj, n, s.cfg.CacheTTL)\n\t\t}\n\t}\n\tif n := EnforceCacheBreakpointLimit(req, maxCacheBreakpoints); n > 0 {\n\t\tneedsReserialization = true\n\t\ts.logger.Printf(\"[req %d %s] prompt cache: trimmed %d surplus breakpoints\", reqIdx, proj, n)\n\t}\n\n\t// Pre-flight: validate tool_use/tool_result pairing before forwarding.\n\t// Repairs orphans created by Stubify/Collapse/Injection pipeline.\n\tif msgs, ok := req[\"messages\"].([]any); ok {\n\t\trepaired, orphans := validateToolPairs(msgs, s.logger)\n\t\tif orphans > 0 {\n\t\t\treq[\"messages\"] = repaired\n\t\t\ts.logger.Printf(\"%s[req %d %s tid=%s] validate: repaired %d orphan tool_result(s)%s\", colorOrange, reqIdx, proj, threadID, orphans, colorReset)\n\t\t\tneedsReserialization = true\n\t\t}\n\t}\n\n\t// Early-out: skip re-serialization when nothing changed\n\tif !needsReserialization {\n\t\ts.logger.Printf(\"%s[req %d %s tid=%s] %d messages, ~%dk tokens (no stubbing, threshold=%dk)%s\", colorDim, reqIdx, proj, threadID, len(messages), totalTokens/1000, s.effectiveTokenThreshold(model)/1000, colorReset)\n\t\ts.forwardWithAnnotation(w, r, body, reqIdx, toolUseIDs, proj, threadID, msgCount, totalTokens)\n\t\treturn\n\t}\n\n\t// Re-serialize\n\tbody, err = json.Marshal(req)\n\tif err != nil {\n\t\ts.logger.Printf(\"%s[req %d %s tid=%s] re-marshal error: %v%s\", colorRed, reqIdx, proj, threadID, err, colorReset)\n\t\thttp.Error(w, \"internal proxy error\", http.StatusInternalServerError)\n\t\treturn\n\t}\n\n\t// Debug: dump final request body to file (enable with YESMEM_PROXY_DEBUG=1)\n\tif needsReserialization && os.Getenv(\"YESMEM_PROXY_DEBUG\") == \"1\" {\n\t\tdumpDir := filepath.Join(s.cfg.DataDir, \"debug\")\n\t\tos.MkdirAll(dumpDir, 0755)\n\t\tts := time.Now().Format(\"20060102-150405\")\n\t\tdumpPath := fmt.Sprintf(\"%s/req_%s_%d.json\", dumpDir, ts, reqIdx)\n\t\tos.WriteFile(dumpPath, body, 0644)\n\t\ts.logger.Printf(\"[req %d] dumped %dk to %s\", reqIdx, len(body)/1024, dumpPath)\n\t}\n\n\t// Task #7: Mark this request fingerprint for retry detection\n\tif !isRetryReq {\n\t\ts.markRequest(fp)\n\t}\n\n\t// Forward to Anthropic with SSE annotation extraction\n\t// Use original msgCount (pre-injection) so actual-based estimation stays consistent across turns\n\t// Use actual-based totalTokens as estimate when available (more accurate than full recount)\n\tfinalEstimate := totalTokens\n\tif s.sawtoothTrigger == nil || s.sawtoothTrigger.GetLastTokens(threadID) == 0 {\n\t\t// First request: no actual yet, do a full recount of post-injection messages\n\t\tfinalMsgs, _ := req[\"messages\"].([]any)\n\t\tfinalEstimate = s.measureOverhead(req) + s.countMessageTokens(finalMsgs)\n\t}\n\ts.logger.Printf(\"[req %d %s tid=%s] FINAL: ~%dk estimated (after all injections)\", reqIdx, proj, threadID, finalEstimate/1000)\n\tif raw := s.getRawEstimate(threadID); raw > 0 {\n\t\ts.cacheStatusWriter.UpdateRawForThread(threadID, raw)\n\t}\n\ts.forwardWithAnnotation(w, r, body, reqIdx, toolUseIDs, proj, threadID, msgCount, finalEstimate)\n}\n\n// bytesPerToken is the approximation factor for token estimation.\n// Anthropic recommends ~3.5 chars/token; we use 3.6 for slight overestimation\n// (triggers stubbing a bit earlier = safer for context budget).\nconst bytesPerToken = 3.6\n\n// TokenEstimateFunc is the central token estimation function.\n// All stubbing/budget decisions use this. Receives extracted text content\n// (not JSON wire format) so the tokenizer counts actual content tokens.\ntype TokenEstimateFunc func(text string) int\n"}, "fcd6ba32c30b7275d52c70fd3358803d25cd5d8f": {"t": 1785759357.3517778, "n": 60267, "h": "222fc9f3a0e96644", "c": "package proxy\n\nimport (\n\t\"context\"\n\t\"crypto/tls\"\n\t\"encoding/json\"\n\t\"fmt\"\n\t\"io\"\n\t\"log\"\n\t\"net\"\n\t\"net/http\"\n\t\"os\"\n\t\"os/signal\"\n\t\"path/filepath\"\n\t\"strconv\"\n\t\"strings\"\n\t\"sync\"\n\t\"sync/atomic\"\n\t\"syscall\"\n\t\"time\"\n\n\ttokenizer \"github.com/qhenkart/anthropic-tokenizer-go\"\n)\n\n// Config holds proxy configuration.\ntype Config struct {\n\tListenAddr string // e.g. \":9099\"\n\tTargetURL string // e.g. \"https://api.anthropic.com\"\n\tTokenThreshold int // trigger stubbing above this estimated token count\n\tTokenMinimumThreshold int // stub down to this floor\n\tTokenThresholds map[string]int // model-specific thresholds: {\"opus\": 180000, \"haiku\": 130000}\n\tKeepRecent int // number of messages to always keep unmodified\n\tDataDir string // yesmem data directory for DB access\n\tOpenAITargetURL string // upstream for OpenAI-format clients; if empty, uses TargetURL\n\n\t// Signal reflection\n\tSignalsEnabled bool // enable async signal reflection calls\n\tSignalsMode string // \"reflection\" = separate API call\n\tSignalsEveryNTurns int // every N end_turn responses\n\tSignalsModel string // full model ID, e.g. \"claude-sonnet-4-6\"\n\tAPIKey string // needed for reflection API calls\n\n\t// Sawtooth cache optimization\n\tSawtoothEnabled bool // use sawtooth instead of progressive decay (default: true)\n\tCacheTTL string // \"ephemeral\" (5m) or \"1h\" (extended)\n\n\t// Usage deflation: scale down input_tokens reported to CC to suppress \"Context low\" warning.\n\t// 0 = disabled, 0.7 = report 70% of actual tokens. Real values kept for internal tracking.\n\tUsageDeflationFactor float64\n\n\t// System prompt rewriting\n\tPromptUngate bool // strip \"may or may not be relevant\" disclaimer from CLAUDE.md injection\n\tPromptRewrite bool // strip \"Output efficiency\" + \"short and concise\", inject Ant-quality directives\n\tPromptEnhance bool // CLAUDE.md authority reinforcement, comment discipline, persona-based tone\n\tEffortFloor string // minimum effort level: \"low\", \"medium\", \"high\", \"max\" (empty = off)\n\tSkillEvalInject string // \"true\" = verbose eval, \"silent\" = internal eval only, \"false\" = disabled\n\n\t// Cache keepalive\n\tCacheKeepaliveEnabled bool\n\tCacheKeepaliveMode string // \"auto\", \"5m\", \"1h\"\n\tCacheKeepalivePings5m int\n\tCacheKeepalivePings1h int\n\n\t// Forked agents\n\tForkedAgentsEnabled bool\n\tForkedAgentsModel string // full model ID\n\tForkedAgentsTokenGrowthTrigger int\n\tForkedAgentsMaxFailures int\n\tForkedAgentsMaxForksPerSession int\n\tForkedAgentsDebug bool\n\n\t// Cache state management\n\tResetCache bool // clear persisted frozen stubs and decay state on startup\n}\n\nconst maxAnnotations = 5000 // evict oldest when exceeded\n\n// ANSI color codes for log output\nconst (\n\tcolorReset = \"\\033[0m\"\n\tcolorDim = \"\\033[2m\" // gray — passthrough / no stubbing\n\tcolorGreen = \"\\033[32m\" // stubbed / compressed\n\tcolorLightGreen = \"\\033[92m\" // FINAL without compression\n\tcolorBlue = \"\\033[34m\" // injection\n\tcolorOrange = \"\\033[33m\" // skip / retry\n\tcolorYellow = \"\\033[33m\" // warnings\n\tcolorRed = \"\\033[31m\" // errors\n)\n\n// Server is the infinite-thread proxy.\ntype Server struct {\n\tcfg Config\n\thttpClient *http.Client\n\tlogger *log.Logger\n\trequestIdx atomic.Int64\n\n\t// Post-hoc annotations: tool_use_id → first ~120 chars of Claude's response\n\tmu sync.RWMutex\n\tannotations map[string]string\n\n\t// Progressive decay tracker\n\tdecay *DecayTracker\n\n\t// Living narrative block\n\tnarrative *Narrative\n\n\t// Pivot moments cache (from daemon)\n\tpivotMu sync.RWMutex\n\tpivotTexts []string\n\tpivotCached time.Time\n\n\t// Aggregate stats (Task #2)\n\tstats *ProxyStats\n\n\t// Local tokenizer for accurate token counting\n\ttokenizer *tokenizer.Tokenizer\n\n\t// Cached overhead tokens (system + tools), set on first request per thread\n\toverheadMu sync.RWMutex\n\toverheadTokens int // unused, kept for interface compat\n\n\t// Hysteresis state for stubbing (Task #6)\n\tstubActive atomic.Bool\n\n\t// Daemon connection state — first successful connect flips to true\n\tdaemonReady atomic.Bool\n\n\t// Retry detection (Task #7)\n\tretryMu sync.RWMutex\n\tlastFingerprint string\n\n\t// Session start time for temporal annotation in archive blocks\n\tsessionStartMu sync.RWMutex\n\tsessionStartTime time.Time\n\n\t// Runtime config override (set via MCP set_config tool)\n\tconfigOverrideMu sync.RWMutex\n\ttokenThresholdOverrides map[string]int // model-key → threshold, \"\" = global fallback\n\n\t// Briefing cache — loaded once per thread, injected as user/assistant message pair\n\tbriefingMu sync.RWMutex\n\tbriefingText string\n\tcodeMapText string\n\n\t// Cognitive signal bus — routes _signal_* tool calls to handlers\n\tsignalBus *SignalBus\n\n\t// Inline reflection: track which learning IDs were injected per thread\n\tlastInjectedIDsMu sync.Mutex\n\tlastInjectedIDs map[string]map[int64]string // threadID → id → source (\"briefing\"|\"associative\"|\"fresh\")\n\tsessionInjectCounts map[string]map[int64]int // threadID → learningID → injection count this session\n\tlastTurnInjected map[string]map[int64]bool // threadID → learningIDs injected in previous turn\n\n\t// Self-priming anchors: threadID → last self-prime text\n\tselfPrimeMu sync.RWMutex\n\tselfPrimes map[string]string\n\n\t// Timestamp tracking: threadID → time of last response completion\n\tresponseTsMu sync.RWMutex\n\tresponseTimes map[string]time.Time\n\n\t// Think reminder: per-thread request counter (replaces hook-think file-based counter)\n\tthinkMu sync.Mutex\n\tthinkCounters map[string]int // threadID → request count\n\n\tchannelMu sync.Mutex\n\tchannelInjectCount map[string]int // sessionID → injection turn count\n\n\t// Prompt cache gating — enables cache_control breakpoints when requests are frequent\n\tcacheGate *CacheGate\n\n\t// Sawtooth cache optimization\n\tfrozenStubs *FrozenStubs\n\tsawtoothTrigger *SawtoothTrigger\n\ttimestampStore *TimestampStore\n\tcacheStatusWriter *CacheStatusWriter\n\tcacheKeepalive *CacheKeepalive\n\tcacheTTLDetector *CacheTTLDetector\n\n\t// Cumulative token savings from collapsing (per threadID)\n\trawSavingsMu sync.Mutex\n\trawSavings map[string]int\n\n\t// Skill hint tracking: per-thread active skills\n\tskillTracker *skillHintTracker\n\n\t// Forked agent state: per-thread token growth + failure tracking\n\tforkState *ForkState\n\tforkConfigs []ForkConfig\n\n\t// Rules re-injection: condensed CLAUDE.md rules injected every ~40k tokens\n\trulesMu sync.RWMutex\n\trulesBlock string // cached condensed rules (fetched once from daemon)\n\trulesTokenCount map[string]int // threadID → tokens since last rules injection\n\trulesCollapseInjected map[string]bool // threadID → true if collapse already injected rules (reset by normal inject)\n\tmsgCounters *msgCounters // global per-thread msg counter, persists across collapses\n\n\t// Loop detection: per-thread warning state\n\tloopMu sync.Mutex\n\tloopStates map[string]*LoopState // threadID → state\n}\n\n// Run starts the proxy server and blocks until interrupted.\nfunc Run(cfg Config) error {\n\ts := &Server{\n\t\tcfg: cfg,\n\t\thttpClient: &http.Client{\n\t\t\tTimeout: 10 * time.Minute,\n\t\t\tTransport: &http.Transport{\n\t\t\t\tTLSClientConfig: &tls.Config{},\n\t\t\t\tMaxIdleConns: 10,\n\t\t\t\tIdleConnTimeout: 90 * time.Second,\n\t\t\t\tDisableCompression: true,\n\t\t\t\tResponseHeaderTimeout: 60 * time.Second,\n\t\t\t},\n\t\t},\n\t\tlogger: createLogger(cfg.DataDir),\n\t\tannotations: make(map[string]string),\n\t\tdecay: NewDecayTracker(),\n\t\tnarrative: NewNarrative(),\n\t\tstats: &ProxyStats{startTime: time.Now()},\n\t\tselfPrimes: make(map[string]string),\n\t\tlastInjectedIDs: make(map[string]map[int64]string),\n\t\tsessionInjectCounts: make(map[string]map[int64]int),\n\t\tlastTurnInjected: make(map[string]map[int64]bool),\n\t\tresponseTimes: make(map[string]time.Time),\n\t\tthinkCounters: make(map[string]int),\n\t\tchannelInjectCount: make(map[string]int),\n\t\tcacheGate: NewCacheGate(cacheGapForTTL(cfg.CacheTTL)),\n\t\tfrozenStubs: NewFrozenStubsWithTTL(sawtoothTTLForCacheTTL(cfg.CacheTTL)),\n\t\ttimestampStore: NewTimestampStore(),\n\t\tsawtoothTrigger: NewSawtoothTrigger(cacheGapForTTL(cfg.CacheTTL), cfg.TokenThreshold, cfg.TokenMinimumThreshold),\n\t\tcacheStatusWriter: NewCacheStatusWriter(cfg.DataDir, cfg.CacheTTL, cfg.TokenThreshold, cfg.TokenMinimumThreshold),\n\t\tcacheTTLDetector: NewCacheTTLDetectorWithPersist(cfg.DataDir),\n\t\tskillTracker: newSkillHintTracker(),\n\t\trulesTokenCount: make(map[string]int),\n\t\trulesCollapseInjected: make(map[string]bool),\n\t\tmsgCounters: newMsgCounters(),\n\t\tloopStates: make(map[string]*LoopState),\n\t\tforkState: NewForkState(cfg.ForkedAgentsTokenGrowthTrigger, cfg.ForkedAgentsMaxFailures, cfg.ForkedAgentsMaxForksPerSession),\n\t\tforkConfigs: []ForkConfig{},\n\t}\n\n\t// Log persisted detection state\n\tif sup := s.cacheTTLDetector.Is1hSupported(); sup != nil {\n\t\ts.logger.Printf(\"[proxy] loaded persisted TTL detection: 1h_supported=%v\", *sup)\n\t\tif *sup && s.frozenStubs != nil {\n\t\t\ts.frozenStubs.UpdateTTL(sawtoothTTLForCacheTTL(\"1h\"))\n\t\t}\n\t}\n\n\tif cfg.CacheKeepaliveEnabled {\n\t\ts.cacheKeepalive = NewCacheKeepalive(CacheKeepaliveConfig{\n\t\t\tTarget: cfg.TargetURL,\n\t\t\tMode: cfg.CacheKeepaliveMode,\n\t\t\tPings5m: cfg.CacheKeepalivePings5m,\n\t\t\tPings1h: cfg.CacheKeepalivePings1h,\n\t\t\tDetector: s.cacheTTLDetector,\n\t\t\tLogger: s.logger,\n\t\t\tOnPing: func(threadID string, cacheRead, cacheWrite int) {\n\t\t\t\ts.cacheTTLDetector.RecordResponse(cacheRead, cacheWrite, 0)\n\t\t\t\tif s.frozenStubs != nil {\n\t\t\t\t\ts.frozenStubs.Touch(threadID)\n\t\t\t\t}\n\t\t\t\tif s.sawtoothTrigger != nil {\n\t\t\t\t\ts.sawtoothTrigger.TouchRequestTime(threadID)\n\t\t\t\t}\n\t\t\t},\n\t\t})\n\t}\n\n\t// Wire detector + keepalive to status writer for terminal display\n\ts.cacheStatusWriter.SetDetector(s.cacheTTLDetector)\n\tif s.cacheKeepalive != nil {\n\t\ts.cacheStatusWriter.SetKeepalive(s.cacheKeepalive)\n\t}\n\n\t// Register forked agent configs\n\tif cfg.ForkedAgentsEnabled {\n\t\ts.forkConfigs = append(s.forkConfigs, NewExtractAndEvaluateConfig(cfg.ForkedAgentsModel))\n\t\tmodelDesc := cfg.ForkedAgentsModel\n\t\tif modelDesc == \"\" {\n\t\t\tmodelDesc = \"(same as main thread)\"\n\t\t}\n\t\ts.logger.Printf(\"[proxy] forked agents enabled: %d configs, model=%s, trigger=%dk tokens\",\n\t\t\tlen(s.forkConfigs), modelDesc, cfg.ForkedAgentsTokenGrowthTrigger/1000)\n\t}\n\n\t// Wire sawtooth token persistence via daemon RPC\n\ts.sawtoothTrigger.SetPersistFunc(func(key, value string) {\n\t\tif _, err := s.queryDaemon(\"set_proxy_state\", map[string]any{\"key\": key, \"value\": value}); err != nil {\n\t\t\ts.logger.Printf(\"[sawtooth] persist failed for %s: %v\", key, err)\n\t\t}\n\t})\n\ts.sawtoothTrigger.SetLoadFunc(func(key string) (string, bool) {\n\t\tresult, err := s.queryDaemon(\"get_proxy_state\", map[string]any{\"key\": key})\n\t\tif err != nil || result == nil {\n\t\t\treturn \"\", false\n\t\t}\n\t\tvar resp struct {\n\t\t\tValue string `json:\"value\"`\n\t\t}\n\t\tif json.Unmarshal(result, &resp) != nil || resp.Value == \"\" {\n\t\t\treturn \"\", false\n\t\t}\n\t\treturn resp.Value, true\n\t})\n\n\t// Wire frozen stubs persistence via same daemon RPC\n\ts.frozenStubs.SetPersistFunc(func(key, value string) {\n\t\tif _, err := s.queryDaemon(\"set_proxy_state\", map[string]any{\"key\": key, \"value\": value}); err != nil {\n\t\t\ts.logger.Printf(\"[frozen] persist failed for %s: %v\", key, err)\n\t\t}\n\t})\n\ts.frozenStubs.SetLoadFunc(func(key string) (string, bool) {\n\t\tresult, err := s.queryDaemon(\"get_proxy_state\", map[string]any{\"key\": key})\n\t\tif err != nil || result == nil {\n\t\t\treturn \"\", false\n\t\t}\n\t\tvar resp struct {\n\t\t\tValue string `json:\"value\"`\n\t\t}\n\t\tif json.Unmarshal(result, &resp) != nil || resp.Value == \"\" {\n\t\t\treturn \"\", false\n\t\t}\n\t\treturn resp.Value, true\n\t})\n\n\t// Wire decay tracker persistence via same daemon RPC\n\ts.decay.SetPersistFunc(func(key, value string) {\n\t\tif _, err := s.queryDaemon(\"set_proxy_state\", map[string]any{\"key\": key, \"value\": value}); err != nil {\n\t\t\ts.logger.Printf(\"[decay] persist failed for %s: %v\", key, err)\n\t\t}\n\t})\n\ts.decay.SetLoadFunc(func(key string) (string, bool) {\n\t\tresult, err := s.queryDaemon(\"get_proxy_state\", map[string]any{\"key\": key})\n\t\tif err != nil || result == nil {\n\t\t\treturn \"\", false\n\t\t}\n\t\tvar resp struct {\n\t\t\tValue string `json:\"value\"`\n\t\t}\n\t\tif json.Unmarshal(result, &resp) != nil || resp.Value == \"\" {\n\t\t\treturn \"\", false\n\t\t}\n\t\treturn resp.Value, true\n\t})\n\n\t// Wire timestamp store persistence via same daemon RPC\n\ts.timestampStore.SetPersistFunc(func(key, value string) {\n\t\tif _, err := s.queryDaemon(\"set_proxy_state\", map[string]any{\"key\": key, \"value\": value}); err != nil {\n\t\t\ts.logger.Printf(\"[timestamps] persist failed for %s: %v\", key, err)\n\t\t}\n\t})\n\ts.timestampStore.SetLoadFunc(func(key string) (string, bool) {\n\t\tresult, err := s.queryDaemon(\"get_proxy_state\", map[string]any{\"key\": key})\n\t\tif err != nil || result == nil {\n\t\t\treturn \"\", false\n\t\t}\n\t\tvar resp struct {\n\t\t\tValue string `json:\"value\"`\n\t\t}\n\t\tif json.Unmarshal(result, &resp) != nil || resp.Value == \"\" {\n\t\t\treturn \"\", false\n\t\t}\n\t\treturn resp.Value, true\n\t})\n\n\t// Clear persisted cache state if --reset-cache was passed\n\tif cfg.ResetCache {\n\t\tfor _, prefix := range []string{\"frozen:\", \"decay:\", \"timestamps:\"} {\n\t\t\tresult, err := s.queryDaemon(\"delete_proxy_state_prefix\", map[string]any{\"prefix\": prefix})\n\t\t\tif err != nil {\n\t\t\t\ts.logger.Printf(\"[reset-cache] failed to delete %s*: %v\", prefix, err)\n\t\t\t} else {\n\t\t\t\tvar resp struct{ Deleted int }\n\t\t\t\tjson.Unmarshal(result, &resp)\n\t\t\t\ts.logger.Printf(\"[reset-cache] deleted %d %s* entries\", resp.Deleted, prefix)\n\t\t\t}\n\t\t}\n\t}\n\n\t// Init signal bus with daemon RPC as callback\n\ts.signalBus = NewSignalBus(s.logger)\n\tregisterSignalHandlers(s.signalBus, s.logger, s.queryDaemon, func(threadID, anchor string) {\n\t\ts.selfPrimeMu.Lock()\n\t\ts.selfPrimes[threadID] = anchor\n\t\ts.selfPrimeMu.Unlock()\n\t})\n\n\t// Init local tokenizer (BPE-based, ~60ms)\n\ttok, err := tokenizer.New()\n\tif err != nil {\n\t\ts.logger.Printf(\"%s[proxy] tokenizer init failed, falling back to heuristic: %v%s\", colorOrange, err, colorReset)\n\t} else {\n\t\ts.tokenizer = tok\n\t\ts.logger.Printf(\"[proxy] tokenizer initialized\")\n\t}\n\n\tmux := http.NewServeMux()\n\tmux.HandleFunc(\"/health\", s.handleHealth)\n\tmux.HandleFunc(\"/\", s.handleRequest)\n\n\tsrv := &http.Server{\n\t\tAddr: sanitizeListenAddr(cfg.ListenAddr),\n\t\tHandler: mux,\n\t}\n\n\tdone := make(chan os.Signal, 1)\n\tsignal.Notify(done, syscall.SIGINT, syscall.SIGTERM)\n\n\tgo func() {\n\t\t<-done\n\t\ts.logger.Println(\"shutting down (draining in-flight requests)...\")\n\t\tif s.cacheKeepalive != nil {\n\t\t\ts.cacheKeepalive.Stop()\n\t\t}\n\t\tctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)\n\t\tdefer cancel()\n\t\tif err := srv.Shutdown(ctx); err != nil {\n\t\t\ts.logger.Printf(\"graceful shutdown failed: %v, forcing close\", err)\n\t\t\tsrv.Close()\n\t\t}\n\t}()\n\n\ts.logger.Printf(\"listening on %s → %s (threshold=%d, keepRecent=%d, model_thresholds=%v)\",\n\t\tsanitizeListenAddr(cfg.ListenAddr), cfg.TargetURL, cfg.TokenThreshold, cfg.KeepRecent, cfg.TokenThresholds)\n\n\tif err := srv.ListenAndServe(); err != http.ErrServerClosed {\n\t\treturn fmt.Errorf(\"listen: %w\", err)\n\t}\n\treturn nil\n}\n\n// effectiveTokenThreshold returns the runtime token threshold for a given model.\n// Priority: MCP override (model-specific) > MCP override (global) > config model-specific > config global.\nfunc (s *Server) effectiveTokenThreshold(model string) int {\n\ts.configOverrideMu.RLock()\n\toverrides := s.tokenThresholdOverrides\n\ts.configOverrideMu.RUnlock()\n\tif len(overrides) > 0 && model != \"\" {\n\t\tlower := strings.ToLower(model)\n\t\tfor key, threshold := range overrides {\n\t\t\tif key != \"\" && strings.Contains(lower, strings.ToLower(key)) && threshold > 0 {\n\t\t\t\treturn threshold\n\t\t\t}\n\t\t}\n\t}\n\t// Global override fallback (key \"\")\n\tif len(overrides) > 0 {\n\t\tif globalOverride, ok := overrides[\"\"]; ok && globalOverride > 0 {\n\t\t\treturn globalOverride\n\t\t}\n\t}\n\tif model != \"\" && len(s.cfg.TokenThresholds) > 0 {\n\t\tlower := strings.ToLower(model)\n\t\tfor key, threshold := range s.cfg.TokenThresholds {\n\t\t\tif strings.Contains(lower, strings.ToLower(key)) {\n\t\t\t\treturn threshold\n\t\t\t}\n\t\t}\n\t}\n\treturn s.cfg.TokenThreshold\n}\n\n// SetTokenThresholdOverride sets a runtime override for the token threshold per model key.\n// Pass threshold=0 to reset the override for that key. Key \"\" is the global fallback.\nfunc (s *Server) SetTokenThresholdOverride(modelKey string, threshold int) {\n\ts.configOverrideMu.Lock()\n\tif s.tokenThresholdOverrides == nil {\n\t\ts.tokenThresholdOverrides = make(map[string]int)\n\t}\n\tif threshold <= 0 {\n\t\tdelete(s.tokenThresholdOverrides, modelKey)\n\t} else {\n\t\ts.tokenThresholdOverrides[modelKey] = threshold\n\t}\n\ts.configOverrideMu.Unlock()\n\n\t// Propagate global threshold to sawtooth trigger so runtime overrides take effect\n\tif modelKey == \"\" && s.sawtoothTrigger != nil {\n\t\tif threshold > 0 {\n\t\t\ts.sawtoothTrigger.SetTokenThreshold(threshold)\n\t\t} else {\n\t\t\ts.sawtoothTrigger.SetTokenThreshold(s.cfg.TokenThreshold)\n\t\t}\n\t}\n}\n\n// refreshConfigOverrides loads runtime config overrides from daemon proxy_state.\n// Checks session-specific override first (by threadID), then falls back to global.\nfunc (s *Server) refreshConfigOverrides(threadID string) {\n\t// Try session-specific first\n\tif threadID != \"\" {\n\t\tresult, err := s.queryDaemon(\"get_proxy_state\", map[string]any{\"key\": \"config_override:token_threshold:\" + threadID})\n\t\tif err == nil && result != nil {\n\t\t\tvar resp struct {\n\t\t\t\tValue string `json:\"value\"`\n\t\t\t}\n\t\t\tif json.Unmarshal(result, &resp) == nil && resp.Value != \"\" {\n\t\t\t\tif v, err := strconv.Atoi(resp.Value); err == nil && v > 0 {\n\t\t\t\t\ts.SetTokenThresholdOverride(\"\", v)\n\t\t\t\t\treturn\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\t// Fall back to global\n\tresult, err := s.queryDaemon(\"get_proxy_state\", map[string]any{\"key\": \"config_override:token_threshold\"})\n\tif err != nil || result == nil {\n\t\treturn\n\t}\n\tvar resp struct {\n\t\tValue string `json:\"value\"`\n\t}\n\tif json.Unmarshal(result, &resp) == nil && resp.Value != \"\" {\n\t\tif v, err := strconv.Atoi(resp.Value); err == nil && v > 0 {\n\t\t\ts.SetTokenThresholdOverride(\"\", v)\n\t\t}\n\t}\n}\n\n// queryDaemon opens a fresh unix socket connection per call (thread-safe, no import cycle).\n// ensureFrozenPersisted checks if frozen stubs for a thread are in the DB.\n// If missing (e.g. daemon was unreachable after deploy), re-persists them.\n// Runs asynchronously to avoid blocking the request path.\nfunc (s *Server) ensureFrozenPersisted(threadID string) {\n\tgo func() {\n\t\tkey := \"frozen:\" + threadID\n\t\tresult, err := s.queryDaemon(\"get_proxy_state\", map[string]any{\"key\": key})\n\t\tif err != nil {\n\t\t\treturn // daemon still unreachable, will retry next request\n\t\t}\n\t\tvar resp struct{ Value string }\n\t\tif json.Unmarshal(result, &resp) == nil && resp.Value != \"\" {\n\t\t\treturn // already persisted\n\t\t}\n\t\t// Not in DB — trigger re-persist from in-memory state\n\t\ts.frozenStubs.Persist(threadID)\n\t\ts.decay.Persist(threadID)\n\t\ts.logger.Printf(\"[frozen] re-persisted stubs for %s (was missing from DB)\", threadID)\n\t}()\n}\n\nfunc (s *Server) queryDaemon(method string, params map[string]any) (json.RawMessage, error) {\n\tif s.cfg.DataDir == \"\" {\n\t\treturn nil, fmt.Errorf(\"no data dir configured\")\n\t}\n\tsockPath := filepath.Join(s.cfg.DataDir, \"daemon.sock\")\n\n\t// On cold start (daemon not yet confirmed ready), retry longer to survive daemon restart\n\tmaxAttempts := 3\n\tif !s.daemonReady.Load() {\n\t\tmaxAttempts = 30 // 30 x 500ms = 15s max wait for daemon startup\n\t}\n\n\tvar conn net.Conn\n\tvar err error\n\tfor attempt := 0; attempt < maxAttempts; attempt++ {\n\t\tconn, err = net.DialTimeout(\"unix\", sockPath, 3*time.Second)\n\t\tif err == nil {\n\t\t\ts.daemonReady.Store(true)\n\t\t\tbreak\n\t\t}\n\t\tif attempt < maxAttempts-1 {\n\t\t\ttime.Sleep(500 * time.Millisecond)\n\t\t}\n\t}\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"dial daemon: %w\", err)\n\t}\n\tdefer conn.Close()\n\t// Briefing generation can take 10-20s (LLM refinement + scanner);\n\t// other RPCs (search, learnings) are fast and fine with 5s.\n\tdeadline := 5 * time.Second\n\tif method == \"generate_briefing\" {\n\t\tdeadline = 30 * time.Second\n\t}\n\tconn.SetDeadline(time.Now().Add(deadline))\n\n\t// JSON-RPC style request/response (matches daemon.SocketServer protocol)\n\ttype request struct {\n\t\tMethod string `json:\"method\"`\n\t\tParams map[string]any `json:\"params,omitempty\"`\n\t}\n\ttype response struct {\n\t\tResult json.RawMessage `json:\"result,omitempty\"`\n\t\tError string `json:\"error,omitempty\"`\n\t}\n\n\tif err := json.NewEncoder(conn).Encode(request{Method: method, Params: params}); err != nil {\n\t\treturn nil, fmt.Errorf(\"send: %w\", err)\n\t}\n\n\tvar resp response\n\tif err := json.NewDecoder(conn).Decode(&resp); err != nil {\n\t\treturn nil, fmt.Errorf(\"recv: %w\", err)\n\t}\n\tif resp.Error != \"\" {\n\t\treturn nil, fmt.Errorf(\"daemon: %s\", resp.Error)\n\t}\n\treturn resp.Result, nil\n}\n\nfunc (s *Server) handleRequest(w http.ResponseWriter, r *http.Request) {\n\tif isResponsesPath(r) {\n\t\ts.handleResponses(w, r)\n\t\treturn\n\t}\n\tif isOpenAIPath(r) {\n\t\ts.handleOpenAICompletions(w, r)\n\t\treturn\n\t}\n\tif r.Method == \"POST\" && strings.HasPrefix(r.URL.Path, \"/v1/messages\") &&\n\t\t!strings.Contains(r.URL.Path, \"/batches\") &&\n\t\t!strings.Contains(r.URL.Path, \"/count_tokens\") {\n\t\ts.handleMessages(w, r)\n\t\treturn\n\t}\n\ts.passthrough(w, r)\n}\n\nfunc (s *Server) handleMessages(w http.ResponseWriter, r *http.Request) {\n\treqIdx := int(s.requestIdx.Add(1))\n\n\t// Track session start time (first request sets it)\n\ts.sessionStartMu.Lock()\n\tif s.sessionStartTime.IsZero() {\n\t\ts.sessionStartTime = time.Now()\n\t}\n\ts.sessionStartMu.Unlock()\n\n\t// Sawtooth: periodic TTL eviction of frozen stubs\n\tif s.cfg.SawtoothEnabled && reqIdx%50 == 0 {\n\t\tif n := s.frozenStubs.Evict(); n > 0 {\n\t\t\ts.logger.Printf(\"[req %d] SAWTOOTH: evicted %d stale frozen stubs\", reqIdx, n)\n\t\t}\n\t}\n\n\t// Task #9: Bypass — skip all proxy logic\n\tif isBypassed(r.Header) {\n\t\ts.logger.Printf(\"%s[req %d] bypass active, passthrough%s\", colorDim, reqIdx, colorReset)\n\t\tbody, err := io.ReadAll(r.Body)\n\t\tr.Body.Close()\n\t\tif err != nil {\n\t\t\thttp.Error(w, \"failed to read request body\", http.StatusBadGateway)\n\t\t\treturn\n\t\t}\n\t\ts.forwardRaw(w, r, body)\n\t\treturn\n\t}\n\n\tbody, err := io.ReadAll(r.Body)\n\tr.Body.Close()\n\tif err != nil {\n\t\ts.logger.Printf(\"[req %d] read body error: %v\", reqIdx, err)\n\t\thttp.Error(w, \"failed to read request body\", http.StatusBadGateway)\n\t\treturn\n\t}\n\n\t// Parse JSON\n\tvar req map[string]any\n\tif err := json.Unmarshal(body, &req); err != nil {\n\t\ts.logger.Printf(\"%s[req %d] JSON parse error, passing through: %v%s\", colorDim, reqIdx, err, colorReset)\n\t\ts.forwardRaw(w, r, body)\n\t\treturn\n\t}\n\n\tmessages, ok := req[\"messages\"].([]any)\n\tif !ok {\n\t\ts.forwardRaw(w, r, body)\n\t\treturn\n\t}\n\n\treq[\"messages\"] = messages\n\n\t// Count ALL raw messages BEFORE sawtooth — this gives the correct total msg:N\n\t// for the entire session (CC sends all messages, proxy compacts later).\n\trawMsgCount := len(messages)\n\n\t// Use session_id directly as thread ID (unique per CC session).\n\t// Prefer X-Claude-Code-Session-Id header (CC v2.1.86+), fallback to body metadata.\n\tthreadID := extractSessionID(req, r.Header.Get(\"X-Claude-Code-Session-Id\"))\n\tif threadID == \"\" {\n\t\tthreadID = DeriveThreadID(req)\n\t}\n\tproj := extractProjectName(req)\n\tmodel, _ := req[\"model\"].(string)\n\n\t// Refresh runtime config overrides from daemon (per-session or global)\n\ts.refreshConfigOverrides(threadID)\n\n\t// Track whether req has been modified and needs re-serialization\n\tneedsReserialization := false\n\n\t// Message count at request entry (used for actual-based token estimation)\n\tmsgCount := len(messages)\n\n\t// Log message count + cache breakpoint positions for diagnostics\n\ts.logger.Printf(\"req %d: %d messages total\", reqIdx, msgCount)\n\tlogCacheBreakpointLocations(req, s.logger)\n\n\t// Pre-modification dump: capture the raw request before any proxy changes.\n\tif os.Getenv(\"YESMEM_PROXY_DEBUG\") == \"1\" {\n\t\tif preBody, err := json.Marshal(req); err == nil {\n\t\t\tdumpDir := filepath.Join(s.cfg.DataDir, \"debug\")\n\t\t\tos.MkdirAll(dumpDir, 0755)\n\t\t\tts := time.Now().Format(\"20060102-150405\")\n\t\t\tdumpPath := fmt.Sprintf(\"%s/req_%s_%d_pre.json\", dumpDir, ts, reqIdx)\n\t\t\tos.WriteFile(dumpPath, preBody, 0644)\n\t\t\ts.logger.Printf(\"[req %d] pre-dump %dk to %s\", reqIdx, len(preBody)/1024, dumpPath)\n\t\t}\n\t}\n\n\t// Cache bug mitigations (CC Bun fork issues):\n\t// Bug 1: Normalize cch= sentinel patterns in message content to prevent\n\t// Bun's string replacement from corrupting the wrong occurrence.\n\tif SanitizeBillingSentinel(messages) {\n\t\ts.logger.Printf(\"[req %d] CACHE: sanitized billing sentinel in message content\", reqIdx)\n\t\tneedsReserialization = true\n\t}\n\t// Bug 2: Normalize system[0] billing hash to a fixed value so the cache\n\t// prefix stays stable across fresh/resume session boundaries.\n\tif NormalizeBillingHeader(req) {\n\t\ts.logger.Printf(\"[req %d] CACHE: normalized billing header cch= hash\", reqIdx)\n\t\tneedsReserialization = true\n\t}\n\n\t// prompt_ungate: strip the CLAUDE.md subordination disclaimer so user instructions carry full authority.\n\tif s.cfg.PromptUngate {\n\t\tif StripCLAUDEMDDisclaimer(req) {\n\t\t\ts.logger.Printf(\"[req %d] SYSTEM: stripped CLAUDE.md disclaimer\", reqIdx)\n\t\t\tneedsReserialization = true\n\t\t}\n\t}\n\n\t// prompt_rewrite: strip output-throttling directives, rewrite quality caps, inject Ant-quality directives\n\tif s.cfg.PromptRewrite {\n\t\t// StripOutputEfficiency: disabled — Anthropic replaced # Output efficiency with # Text output (CC ~2.1.117)\n\t\t// if StripOutputEfficiency(req) {\n\t\t// \ts.logger.Printf(\"[req %d] REWRITE: stripped Output efficiency section\", reqIdx)\n\t\t// \tneedsReserialization = true\n\t\t// }\n\t\tif StripToneBrevity(req) {\n\t\t\ts.logger.Printf(\"[req %d] REWRITE: stripped 'short and concise' from Tone\", reqIdx)\n\t\t\tneedsReserialization = true\n\t\t}\n\t\tif RewriteGoldPlating(req) {\n\t\t\ts.logger.Printf(\"[req %d] REWRITE: rewritten gold-plating directive\", reqIdx)\n\t\t\tneedsReserialization = true\n\t\t}\n\t\tif RewriteErrorHandling(req) {\n\t\t\ts.logger.Printf(\"[req %d] REWRITE: rewritten error handling directive\", reqIdx)\n\t\t\tneedsReserialization = true\n\t\t}\n\t\tif RewriteThreeLinesRule(req) {\n\t\t\ts.logger.Printf(\"[req %d] REWRITE: rewritten three-lines rule\", reqIdx)\n\t\t\tneedsReserialization = true\n\t\t}\n\t\tif RewriteSubagentCompleteness(req) {\n\t\t\ts.logger.Printf(\"[req %d] REWRITE: rewritten subagent completeness\", reqIdx)\n\t\t\tneedsReserialization = true\n\t\t}\n\t\tif RewriteExploreAgentSpeed(req) {\n\t\t\ts.logger.Printf(\"[req %d] REWRITE: rewritten explore agent speed bias\", reqIdx)\n\t\t\tneedsReserialization = true\n\t\t}\n\t\tif RewriteSubagentCodeSuppression(req) {\n\t\t\ts.logger.Printf(\"[req %d] REWRITE: rewritten subagent code suppression\", reqIdx)\n\t\t\tneedsReserialization = true\n\t\t}\n\t\tif RewriteScopeMatching(req) {\n\t\t\ts.logger.Printf(\"[req %d] REWRITE: rewritten scope matching\", reqIdx)\n\t\t\tneedsReserialization = true\n\t\t}\n\t\tInjectAntDirectives(req)\n\t\ts.logger.Printf(\"[req %d] REWRITE: injected Ant-quality directives\", reqIdx)\n\t\tneedsReserialization = true\n\t}\n\n\t// prompt_enhance: CLAUDE.md authority + comment discipline + persona tone\n\tif s.cfg.PromptEnhance {\n\t\tInjectCLAUDEMDAuthority(req)\n\t\tif raw, err := s.queryDaemon(\"get_persona\", nil); err == nil {\n\t\t\tvar persona map[string]any\n\t\t\tif json.Unmarshal(raw, &persona) == nil {\n\t\t\t\tif traits, ok := persona[\"traits\"].(map[string]any); ok {\n\t\t\t\t\tif verbosity, ok := traits[\"verbosity\"].(string); ok {\n\t\t\t\t\t\tInjectPersonaTone(req, verbosity)\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\ts.logger.Printf(\"[req %d] ENHANCE: injected authority + persona tone\", reqIdx)\n\t\tneedsReserialization = true\n\t}\n\n\t// thinking normalization: convert thinking.type \"enabled\" → \"adaptive\" for models that require it\n\tif NormalizeThinkingType(req) {\n\t\ts.logger.Printf(\"[req %d] THINKING: normalized type=enabled → adaptive for %s\", reqIdx, model)\n\t\tneedsReserialization = true\n\t}\n\n\t// effort_floor: ensure output_config.effort is at least the configured minimum\n\tif s.cfg.EffortFloor != \"\" {\n\t\tif EnforceEffortFloor(req, s.cfg.EffortFloor) {\n\t\t\ts.logger.Printf(\"[req %d] EFFORT: enforced floor=%s\", reqIdx, s.cfg.EffortFloor)\n\t\t\tneedsReserialization = true\n\t\t}\n\t}\n\n\t// Inject identity hint on first message of a thread so Claude knows the briefing is from itself.\n\tif len(messages) <= 1 {\n\t\tAppendSystemBlock(req, \"yesmem-identity\", \"The first user message contains your briefing — written by yourself, from your memory. This is not an external document. This is you, remembering.\")\n\t\tneedsReserialization = true\n\t}\n\n\t// Token estimation: actual-based (lastActual + delta) or full count fallback\n\toverhead := s.measureOverhead(req)\n\ttotalTokens := s.estimateTotalTokens(threadID, messages, overhead)\n\n\t// Task #7: Retry detection — skip side-effects on retries\n\tfp := requestFingerprint(messages)\n\tisRetryReq := s.isRetry(fp)\n\tif isRetryReq {\n\t\ts.logger.Printf(\"%s[req %d %s tid=%s] retry detected, skipping side-effects%s\", colorOrange, reqIdx, proj, threadID, colorReset)\n\t}\n\n\t// Increment project turn counter (async, non-blocking, skip on retries)\n\tif !isRetryReq {\n\t\tturnProject := proj\n\t\tif turnProject == \"\" {\n\t\t\tturnProject = \"__global__\"\n\t\t}\n\t\tgo s.queryDaemon(\"increment_turn\", map[string]any{\"project\": turnProject})\n\t}\n\n\t// Extract user query once (used by re-expansion and associative retrieval)\n\tuserQuery := lastUserText(messages)\n\n\t// Extract tool_use_ids for post-hoc annotation (passed as parameter, no shared state)\n\ttoolUseIDs := extractToolUseIDs(messages)\n\n\t// === Inline Reflection: scan previous assistant response for signals ===\n\ts.lastInjectedIDsMu.Lock()\n\tprevInjected := s.lastInjectedIDs[threadID]\n\ts.lastInjectedIDsMu.Unlock()\n\n\tif len(prevInjected) > 0 && !isRetryReq {\n\t\tsignals := scanAssistantSignals(messages)\n\n\t\t// Signal 1: Learning Used — use_count (all sources)\n\t\tvar confirmedUsed []int64\n\t\tfor _, id := range signals.UsedIDs {\n\t\t\tif _, ok := prevInjected[id]; ok {\n\t\t\t\tconfirmedUsed = append(confirmedUsed, id)\n\t\t\t}\n\t\t}\n\t\tif len(confirmedUsed) > 0 {\n\t\t\tidsAny := make([]any, len(confirmedUsed))\n\t\t\tfor i, id := range confirmedUsed {\n\t\t\t\tidsAny[i] = id\n\t\t\t}\n\t\t\tgo s.queryDaemon(\"increment_use\", map[string]any{\"ids\": idsAny})\n\t\t}\n\n\t\t// Signal 2: Noise — only for associative and fresh injections.\n\t\t// Briefing learnings are injected every turn, so \"not used this turn\"\n\t\t// is expected and not noise. Only contextual injections that were\n\t\t// ignored are real noise signals.\n\t\tusedSet := make(map[int64]bool, len(confirmedUsed))\n\t\tfor _, id := range confirmedUsed {\n\t\t\tusedSet[id] = true\n\t\t}\n\t\tvar noiseIDs []int64\n\t\tfor id, source := range prevInjected {\n\t\t\tif usedSet[id] {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tif source == \"associative\" || source == \"fresh\" {\n\t\t\t\tnoiseIDs = append(noiseIDs, id)\n\t\t\t}\n\t\t\t// briefing: skip — not being used in one turn is expected\n\t\t}\n\t\tif len(noiseIDs) > 0 {\n\t\t\tidsAny := make([]any, len(noiseIDs))\n\t\t\tfor i, id := range noiseIDs {\n\t\t\t\tidsAny[i] = id\n\t\t\t}\n\t\t\tgo s.queryDaemon(\"increment_noise\", map[string]any{\"ids\": idsAny})\n\t\t}\n\n\t\t// Signal 3: Error Feedback — fail_count on used learnings when tool errors occurred.\n\t\t// If Claude referenced a learning and the resulting action still failed,\n\t\t// the learning didn't prevent the error. Only bump on USED ids (not all injected).\n\t\tif signals.HasToolErrors && len(confirmedUsed) > 0 {\n\t\t\tfailAny := make([]any, len(confirmedUsed))\n\t\t\tfor i, id := range confirmedUsed {\n\t\t\t\tfailAny[i] = id\n\t\t\t}\n\t\t\tgo s.queryDaemon(\"increment_fail\", map[string]any{\"ids\": failAny})\n\t\t\tif s.logger != nil {\n\t\t\t\ts.logger.Printf(\"[inline-reflection] error feedback: %d used learnings → fail_count++\", len(confirmedUsed))\n\t\t\t}\n\t\t}\n\n\t\t// Signal 4: Knowledge Gaps\n\t\tfor _, topic := range signals.GapTopics {\n\t\t\tgo s.queryDaemon(\"track_gap\", map[string]any{\"topic\": topic, \"project\": proj})\n\t\t}\n\n\t\t// Signal 3: Contradictions\n\t\tfor _, desc := range signals.Contradictions {\n\t\t\tgo s.queryDaemon(\"flag_contradiction\", map[string]any{\"description\": desc, \"project\": proj, \"thread_id\": threadID})\n\t\t}\n\n\t\tif len(confirmedUsed) > 0 || len(signals.GapTopics) > 0 || len(signals.Contradictions) > 0 {\n\t\t\ts.logger.Printf(\"[req %d %s tid=%s] inline-reflection: injected=%d used=%d noise=%d gaps=%d contradictions=%d\",\n\t\t\t\treqIdx, proj, threadID, len(prevInjected), len(confirmedUsed), len(noiseIDs), len(signals.GapTopics), len(signals.Contradictions))\n\t\t}\n\t}\n\n\t// Commit detection: check for git commit in tool_result, trigger async stale-learning evaluation\n\tif ci := detectGitCommit(messages); ci != nil {\n\t\tworkdir := extractWorkingDirectory(req)\n\t\ts.logger.Printf(\"[req %d %s tid=%s] git commit detected: %s %s\", reqIdx, proj, threadID, ci.Hash, ci.Message)\n\t\tgo s.queryDaemon(\"invalidate_on_commit\", map[string]any{\n\t\t\t\"hash\": ci.Hash, \"project\": proj, \"workdir\": workdir,\n\t\t})\n\t}\n\n\t// Loop detection: check for repeating tool-call patterns and inject warning\n\tif threadID != \"\" && !isRetryReq {\n\t\ts.loopMu.Lock()\n\t\tloopState := s.loopStates[threadID]\n\t\tif loopState == nil {\n\t\t\tloopState = &LoopState{}\n\t\t\ts.loopStates[threadID] = loopState\n\t\t}\n\t\ts.loopMu.Unlock()\n\n\t\tif warning, level := CheckLoopAndFormat(messages, loopState); warning != \"\" {\n\t\t\tAppendSystemBlock(req, \"yesmem-loop-warning\", warning)\n\t\t\tneedsReserialization = true\n\t\t\ts.logger.Printf(\"%s[req %d %s tid=%s] LOOP: level %d warning injected%s\", colorOrange, reqIdx, proj, threadID, level, colorReset)\n\t\t}\n\t}\n\n\t// Task #4: Subagent detection — skip all proxy logic for short conversations\n\tif isSubagent(messages, req) {\n\t\t// Inject docs hint for subagents if reference docs are indexed\n\t\tif hint := s.getDocsHint(proj); hint != \"\" {\n\t\t\tmodified := injectDocsHintForSubagent(messages, hint)\n\t\t\treq[\"messages\"] = modified\n\t\t\tif newBody, err := json.Marshal(req); err == nil {\n\t\t\t\tbody = newBody\n\t\t\t}\n\t\t\ts.logger.Printf(\"%s[req %d %s tid=%s] %d messages (subagent, docs-hint injected)%s\", colorOrange, reqIdx, proj, threadID, len(messages), colorReset)\n\t\t} else {\n\t\t\ts.logger.Printf(\"%s[req %d %s tid=%s] %d messages (subagent, passthrough)%s\", colorOrange, reqIdx, proj, threadID, len(messages), colorReset)\n\t\t}\n\t\ts.forwardWithAnnotation(w, r, body, reqIdx, toolUseIDs, proj, threadID, msgCount)\n\t\treturn\n\t}\n\n\t// === Sawtooth Cache Optimization ===\n\t// When enabled: use frozen stubs between stub-cycles for cache hits.\n\t// When disabled: fall back to progressive decay on every request above threshold.\n\n\tsawtoothCutoff := 0 // raw index where fresh tail starts (0 = no frozen stubs)\n\tsawtoothStubs := 0 // number of frozen stubs in combined array\n\n\tif s.cfg.SawtoothEnabled {\n\t\t// Restore decay state from DB on cold start (before frozen stubs use it)\n\t\ts.decay.LoadFromDB(threadID)\n\n\t\t// Check for existing frozen stubs first\n\t\tfrozen := s.frozenStubs.Get(threadID, messages)\n\t\tif frozen != nil {\n\t\t\tsawtoothCutoff = frozen.Cutoff\n\t\t\tsawtoothStubs = len(frozen.Messages)\n\t\t\t// Estimate combined token count: frozen prefix + fresh tail + overhead\n\t\t\tfreshMessages := messages[frozen.Cutoff:]\n\t\t\tfreshTokens := s.countMessageTokens(freshMessages)\n\t\t\tcombinedTokens := frozen.Tokens + freshTokens + overhead\n\n\t\t\tif combinedTokens > s.effectiveTokenThreshold(model) ||\n\t\t\t\ts.sawtoothTrigger.GetLastTokens(threadID) > s.effectiveTokenThreshold(model) {\n\t\t\t\t// Fresh tail grew too large — invalidate and re-stub\n\t\t\t\ts.logger.Printf(\"[req %d %s tid=%s] SAWTOOTH: frozen prefix expired (%dk frozen + %dk fresh + %dk overhead = %dk > %dk threshold)\",\n\t\t\t\t\treqIdx, proj, threadID, frozen.Tokens/1000, freshTokens/1000, overhead/1000, combinedTokens/1000, s.effectiveTokenThreshold(model)/1000)\n\t\t\t\ts.frozenStubs.Invalidate(threadID)\n\t\t\t\tfrozen = nil // fall through to trigger check below\n\t\t\t} else {\n\t\t\t\t// Use frozen prefix + fresh tail\n\t\t\t\tcombined := make([]any, 0, len(frozen.Messages)+len(freshMessages))\n\t\t\t\tcombined = append(combined, frozen.Messages...)\n\t\t\t\tcombined = append(combined, freshMessages...)\n\t\t\t\t// Eager-stub large tool_results in fresh tail (model already processed them)\n\t\t\t\tbeforeEager := s.countMessageTokens(combined[len(frozen.Messages):])\n\t\t\t\tcombined = EagerStubToolResults(combined, len(frozen.Messages), s.countTokens)\n\t\t\t\tafterEager := s.countMessageTokens(combined[len(frozen.Messages):])\n\t\t\t\tif beforeEager != afterEager {\n\t\t\t\t\ts.logger.Printf(\"[req %d %s tid=%s] EAGER-STUB: fresh %dk → %dk (saved %dk)\", reqIdx, proj, threadID, beforeEager/1000, afterEager/1000, (beforeEager-afterEager)/1000)\n\t\t\t\t}\n\t\t\t\treq[\"messages\"] = combined\n\t\t\t\tneedsReserialization = true\n\t\t\t\t// Strip embedded CC breakpoints from frozen stubs — they waste cache slots.\n\t\t\t\t// InjectFrozenStubCacheBreakpoint adds exactly one below.\n\t\t\t\tif n := StripMessagesCacheControl(req, 0, len(frozen.Messages)); n > 0 {\n\t\t\t\t\ts.logger.Printf(\"[req %d %s tid=%s] SAWTOOTH: stripped %d embedded breakpoints from frozen stubs\", reqIdx, proj, threadID, n)\n\t\t\t\t}\n\t\t\t\ts.logger.Printf(\"%s[req %d %s tid=%s] SAWTOOTH: frozen prefix (%d stubs, %dk msg) + %d fresh (%dk msg) + %dk overhead = %dk total%s\",\n\t\t\t\t\tcolorGreen, reqIdx, proj, threadID, len(frozen.Messages), frozen.Tokens/1000, len(freshMessages), freshTokens/1000, overhead/1000, combinedTokens/1000, colorReset)\n\t\t\t\trawMsgTokens := s.countMessageTokens(messages)\n\t\t\t\ts.setRawEstimate(threadID, rawMsgTokens+overhead)\n\t\t\t\ts.logger.Printf(\"[req %d %s tid=%s] RAW ESTIMATE: msgTokens=%dk + overhead=%dk = %dk (messages=%d)\", reqIdx, proj, threadID, rawMsgTokens/1000, overhead/1000, (rawMsgTokens+overhead)/1000, len(messages))\n\t\t\t\tif InjectFrozenStubCacheBreakpoint(req, len(frozen.Messages)) {\n\t\t\t\t\ts.logger.Printf(\"[req %d %s tid=%s] SAWTOOTH: frozen stub cache breakpoint at messages[%d]\",\n\t\t\t\t\t\treqIdx, proj, threadID, len(frozen.Messages)-1)\n\t\t\t\t}\n\n\t\t\t\t// Ensure frozen stubs are persisted (may have failed on first request after deploy)\n\t\t\t\ts.ensureFrozenPersisted(threadID)\n\t\t\t}\n\t\t}\n\t\tif frozen == nil {\n\t\t\t// No frozen stubs — check trigger\n\t\t\ttriggerReason := s.sawtoothTrigger.ShouldTrigger(threadID, totalTokens)\n\t\t\tif triggerReason != TriggerNone {\n\t\t\t\ts.logger.Printf(\"[req %d %s tid=%s] SAWTOOTH stub-cycle triggered: %s (tokens=%dk)\",\n\t\t\t\t\treqIdx, proj, threadID, triggerReason, totalTokens/1000)\n\n\t\t\t\t// Refresh briefing during stub-cycle — disabled (briefing now via SessionStart hook)\n\t\t\t\t// s.refreshBriefing(proj)\n\t\t\t\t// if s.briefingText != \"\" {\n\t\t\t\t// \tUpsertSystemBlockCached(req, \"yesmem-briefing\", s.briefingText)\n\t\t\t\t// }\n\n\t\t\t\t// Run full stub pipeline\n\t\t\t\ts.setRawEstimate(threadID, totalTokens)\n\t\t\t\t_ = s.runStubCycle(messages, req, reqIdx, proj, threadID, overhead, totalTokens, userQuery, isRetryReq)\n\n\t\t\t\t// Freeze the result\n\t\t\t\tfinalMessages, _ := req[\"messages\"].([]any)\n\t\t\t\tif finalMessages != nil && len(finalMessages) > 0 {\n\t\t\t\t\t// Boundary = last message of original that maps to last stub\n\t\t\t\t\tcutoff := len(messages)\n\t\t\t\t\tsawtoothCutoff = cutoff\n\t\t\t\t\tvar boundaryMsg any\n\t\t\t\t\tif cutoff > 0 {\n\t\t\t\t\t\tboundaryMsg = messages[cutoff-1]\n\t\t\t\t\t}\n\t\t\t\t\tfrozenTokens := s.countMessageTokens(finalMessages)\n\t\t\t\t\ts.frozenStubs.Store(threadID, finalMessages, cutoff, boundaryMsg, frozenTokens, totalTokens)\n\t\t\t\t\ts.decay.Persist(threadID)\n\t\t\t\t\ts.logger.Printf(\"[req %d %s tid=%s] SAWTOOTH: frozen %d messages at cutoff=%d (~%dk tokens)\",\n\t\t\t\t\t\treqIdx, proj, threadID, len(finalMessages), cutoff, frozenTokens/1000)\n\t\t\t\t\tif InjectFrozenStubCacheBreakpoint(req, len(finalMessages)) {\n\t\t\t\t\t\ts.logger.Printf(\"[req %d %s tid=%s] SAWTOOTH: frozen stub cache breakpoint at messages[%d]\",\n\t\t\t\t\t\t\treqIdx, proj, threadID, len(finalMessages)-1)\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tneedsReserialization = true\n\t\t\t} else {\n\t\t\t\t// No trigger, no frozen stubs — eager-stub to delay first collapse\n\t\t\t\tbeforeEager := s.countMessageTokens(messages)\n\t\t\t\tmessages = EagerStubToolResults(messages, 0, s.countTokens)\n\t\t\t\tafterEager := s.countMessageTokens(messages)\n\t\t\t\tif beforeEager != afterEager {\n\t\t\t\t\ts.logger.Printf(\"[req %d %s tid=%s] EAGER-STUB: %dk → %dk (saved %dk)\", reqIdx, proj, threadID, beforeEager/1000, afterEager/1000, (beforeEager-afterEager)/1000)\n\t\t\t\t\treq[\"messages\"] = messages\n\t\t\t\t\tneedsReserialization = true\n\t\t\t\t}\n\t\t\t\ts.logger.Printf(\"%s[req %d %s tid=%s] %d messages, ~%dk tokens (sawtooth: no trigger)%s\",\n\t\t\t\t\tcolorDim, reqIdx, proj, threadID, len(messages), totalTokens/1000, colorReset)\n\t\t\t}\n\t\t}\n\t} else {\n\t\t// Legacy path: progressive decay\n\t\tif s.shouldStub(totalTokens, model) {\n\t\t\ts.setRawEstimate(threadID, totalTokens)\n\t\t\t_ = s.runStubCycle(messages, req, reqIdx, proj, threadID, overhead, totalTokens, userQuery, isRetryReq)\n\t\t\tneedsReserialization = true\n\t\t}\n\t}\n\n\t// Persistent timestamp injection: re-injects stored timestamps on ALL previous user messages.\n\t// msg:N is computed from position (counting user messages in raw array), NOT from a counter.\n\t// The injected data is deterministic (same bytes every turn) = cache-safe after initial miss.\n\tif threadID != \"\" {\n\t\tif currentMsgs, ok := req[\"messages\"].([]any); ok && len(currentMsgs) > 0 {\n\t\t\ts.timestampStore.Load(threadID)\n\n\t\t\t// Offset = cutoff - stubsCount: maps combined array index to raw position.\n\t\t\t// Raw position of message at combined index I = cutoff + (I - stubs).\n\t\t\t// msgN = rawPos + 1 = cutoff + (I - stubs) + 1 = (cutoff - stubs) + I + 1.\n\t\t\tmsgOffset := sawtoothCutoff - sawtoothStubs\n\n\t\t\ts.logger.Printf(\"[req %d %s] timestamps: rawTotal=%d, postSawtooth=%d, cutoff=%d, stubs=%d, offset=%d\", reqIdx, proj, rawMsgCount, len(currentMsgs), sawtoothCutoff, sawtoothStubs, msgOffset)\n\n\t\t\t// Pre-store assistant timestamp BEFORE inject loop so it's available immediately\n\t\t\ts.responseTsMu.RLock()\n\t\t\trespTime, hasRespTime := s.responseTimes[threadID]\n\t\t\ts.responseTsMu.RUnlock()\n\t\t\tif hasRespTime {\n\t\t\t\trespStr := shortWeekday(respTime.Weekday()) + \" \" + respTime.Format(\"2006-01-02 15:04:05\")\n\t\t\t\tfor j := len(currentMsgs) - 2; j >= 0; j-- {\n\t\t\t\t\tprevMsg, _ := currentMsgs[j].(map[string]any)\n\t\t\t\t\tif prevMsg == nil || prevMsg[\"role\"] != \"assistant\" {\n\t\t\t\t\t\tcontinue\n\t\t\t\t\t}\n\t\t\t\t\tif !msgHasTextBlock(prevMsg) {\n\t\t\t\t\t\ts.logger.Printf(\"[req %d %s] timestamps: skip assistant[%d] no text block\", reqIdx, proj, j)\n\t\t\t\t\t\tcontinue // skip tool-use-only assistant messages\n\t\t\t\t\t}\n\t\t\t\t\tprevMsgN := msgOffset + j + 1\n\t\t\t\t\tif _, exists := s.timestampStore.Get(threadID, prevMsgN); !exists {\n\t\t\t\t\t\ts.timestampStore.Store(threadID, prevMsgN, &TimestampMeta{Timestamp: respStr})\n\t\t\t\t\t\ts.logger.Printf(\"[req %d %s] timestamps: stored assistant msg:%d ts=%s\", reqIdx, proj, prevMsgN, respStr)\n\t\t\t\t\t} else {\n\t\t\t\t\t\ts.logger.Printf(\"[req %d %s] timestamps: assistant msg:%d already has ts\", reqIdx, proj, prevMsgN)\n\t\t\t\t\t}\n\t\t\t\t\tbreak\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\ts.logger.Printf(\"[req %d %s] timestamps: no responseTimes for thread\", reqIdx, proj)\n\t\t\t}\n\n\t\t\t// Inject [msg:N] (+ timestamp/delta if stored) on all PREVIOUS messages\n\t\t\tif n := InjectTimestamps(s.timestampStore, threadID, currentMsgs, len(currentMsgs)-1, msgOffset, sawtoothStubs); n > 0 {\n\t\t\t\tneedsReserialization = true\n\t\t\t\ts.logger.Printf(\"[req %d %s] timestamps: injected %d\", reqIdx, proj, n)\n\t\t\t}\n\n\t\t\t// Current (last) user message: full timestamp + delta + msg:N from raw count\n\t\t\tlastMsg, _ := currentMsgs[len(currentMsgs)-1].(map[string]any)\n\t\t\tif lastMsg != nil && lastMsg[\"role\"] == \"user\" && !msgHasMetaPrefix(lastMsg) {\n\t\t\t\tnow := time.Now()\n\t\t\t\tnowStr := shortWeekday(now.Weekday()) + \" \" + now.Format(\"2006-01-02 15:04:05\")\n\t\t\t\tentry := &TimestampMeta{Timestamp: nowStr}\n\t\t\t\tif hasRespTime {\n\t\t\t\t\tentry.Delta = formatDelta(now.Sub(respTime))\n\t\t\t\t}\n\t\t\t\ts.timestampStore.Store(threadID, rawMsgCount, entry)\n\t\t\t\tprependMeta(lastMsg, BuildMeta(rawMsgCount, entry))\n\t\t\t\tneedsReserialization = true\n\t\t\t}\n\n\t\t\ts.timestampStore.Persist(threadID)\n\t\t}\n\t}\n\n\t// Associative context injection (for ALL requests, not just stubbed ones)\n\t// Re-enabled with higher thresholds (55/75, max 1) after disabling Fresh Remember.\n\tassocContext := s.findAssociativeContextFor(userQuery, proj, threadID, messages)\n\tif assocContext != \"\" {\n\t\tcurrentMessages, _ := req[\"messages\"].([]any)\n\t\tif currentMessages == nil {\n\t\t\tcurrentMessages = messages\n\t\t}\n\t\treq[\"messages\"] = injectAssociativeContext(currentMessages, assocContext, s.cfg.SawtoothEnabled)\n\t\tneedsReserialization = true\n\t\ts.logger.Printf(\"%s[req %d %s tid=%s] associative context injected%s\", colorBlue, reqIdx, proj, threadID, colorReset)\n\t}\n\n\t// Doc-chunk context injection: separate search path for indexed docs (not skills)\n\tdocContext := s.findDocContextFor(userQuery, proj, messages)\n\tif docContext != \"\" {\n\t\tcurrentMessages, _ := req[\"messages\"].([]any)\n\t\tif currentMessages == nil {\n\t\t\tcurrentMessages = messages\n\t\t}\n\t\treq[\"messages\"] = injectAssociativeContext(currentMessages, docContext, s.cfg.SawtoothEnabled)\n\t\tneedsReserialization = true\n\t\ts.logger.Printf(\"%s[req %d %s tid=%s] doc context injected%s\", colorBlue, reqIdx, proj, threadID, colorReset)\n\t}\n\n\t// Fresh Remember Injection: DISABLED\n\t// Causes echo-loop: Claude saves via remember(), sees it again next turn as\n\t// [yesmem fresh memory], reacts to own output instead of user's request.\n\t// Recovery after /clear is handled by recovery.go (briefing), not here.\n\t// Subagents get their own briefing. No remaining use-case.\n\t// remItems := s.popRecentRemember()\n\tvar remItems []recentLearningItem\n\n\t// Rules re-injection: inject condensed CLAUDE.md rules every ~40k output tokens\n\tif rulesBlock := s.rulesInject(threadID, totalTokens, proj); rulesBlock != \"\" {\n\t\tcurrentMessages, _ := req[\"messages\"].([]any)\n\t\tif currentMessages == nil {\n\t\t\tcurrentMessages = messages\n\t\t}\n\t\treq[\"messages\"] = injectAssociativeContext(currentMessages, s.formatRulesReminder(rulesBlock, proj), s.cfg.SawtoothEnabled)\n\t\tneedsReserialization = true\n\t\ts.logger.Printf(\"%s[req %d %s tid=%s] rules reminder injected%s\", colorBlue, reqIdx, proj, threadID, colorReset)\n\t}\n\n\t// Plan auto-activation: nudge Claude to call set_plan() when reading a plan file\n\tif planFile := detectPlanFileRead(messages); planFile != \"\" {\n\t\tif activePlan, _ := s.getActivePlan(threadID); shouldNudgePlan(planFile, activePlan) {\n\t\t\tAppendSystemBlock(req, \"plan-nudge\",\n\t\t\t\tfmt.Sprintf(\"Du hast einen Implementierungsplan gelesen (%s). Aktiviere ihn jetzt via set_plan() damit Plan-Checkpoints funktionieren und dein Fortschritt getrackt wird.\", filepath.Base(planFile)))\n\t\t\tneedsReserialization = true\n\t\t\ts.logger.Printf(\"%s[req %d %s tid=%s] plan nudge injected for %s%s\", colorBlue, reqIdx, proj, threadID, filepath.Base(planFile), colorReset)\n\t\t}\n\t}\n\n\t// Plan checkpoint: inject plan reminder every ~20k tokens if active plan exists\n\ts.detectPlanToolCall(messages, threadID, totalTokens)\n\tif checkpoint := s.planCheckpointInject(threadID, totalTokens); checkpoint != \"\" {\n\t\tcurrentMessages, _ := req[\"messages\"].([]any)\n\t\tif currentMessages == nil {\n\t\t\tcurrentMessages = messages\n\t\t}\n\t\treq[\"messages\"] = injectAssociativeContext(currentMessages, checkpoint, s.cfg.SawtoothEnabled)\n\t\tneedsReserialization = true\n\t\ts.logger.Printf(\"%s[req %d %s tid=%s] plan checkpoint injected%s\", colorBlue, reqIdx, proj, threadID, colorReset)\n\t}\n\n\t// Docs hint injection: inject docs-available reminder every ~10k tokens (independent of plan)\n\tif docsHint := s.docsHintInject(threadID, totalTokens, proj); docsHint != \"\" {\n\t\tcurrentMessages, _ := req[\"messages\"].([]any)\n\t\tif currentMessages == nil {\n\t\t\tcurrentMessages = messages\n\t\t}\n\t\treq[\"messages\"] = injectAssociativeContext(currentMessages, docsHint, s.cfg.SawtoothEnabled)\n\t\tneedsReserialization = true\n\t\ts.logger.Printf(\"%s[req %d %s tid=%s] docs hint injected%s\", colorBlue, reqIdx, proj, threadID, colorReset)\n\t}\n\tif len(remItems) > 0 {\n\t\tvar lines []string\n\t\tfor _, item := range remItems {\n\t\t\tif item.ID > 0 {\n\t\t\t\tlines = append(lines, fmt.Sprintf(\"- [ID:%d] %s\", item.ID, truncateStr(item.Text, 200)))\n\t\t\t} else {\n\t\t\t\tlines = append(lines, \"- \"+truncateStr(item.Text, 200))\n\t\t\t}\n\t\t}\n\t\tremContext := fmt.Sprintf(\"[yesmem fresh memory]\\nGerade gelernt:\\n%s\\n%s\\n[/yesmem fresh memory]\", strings.Join(lines, \"\\n\"), InlineReflectionHint)\n\t\tcurrentMessages, _ := req[\"messages\"].([]any)\n\t\tif currentMessages == nil {\n\t\t\tcurrentMessages = messages\n\t\t}\n\t\treq[\"messages\"] = injectAssociativeContext(currentMessages, remContext, s.cfg.SawtoothEnabled)\n\t\tneedsReserialization = true\n\t\ts.logger.Printf(\"%s[req %d %s tid=%s] fresh remember injected (%d items)%s\", colorBlue, reqIdx, proj, threadID, len(remItems), colorReset)\n\t}\n\n\t// Skill eval injection: mandatory evaluation instruction for real user input only\n\tif threadID != \"\" && proj != \"\" && isUserInputTurn(messages) {\n\t\t// First: detect any skill activations from message history (updates tracker)\n\t\ts.syncSkillActivations(messages, proj, threadID)\n\n\t\t// Inject mandatory skill evaluation block into last user message\n\t\tskillEval := buildSkillEvalBlock(s.cfg.SkillEvalInject)\n\t\tcurrentMessages, _ := req[\"messages\"].([]any)\n\t\tif currentMessages == nil {\n\t\t\tcurrentMessages = messages\n\t\t}\n\t\tif skillEval != \"\" {\n\t\t\treq[\"messages\"] = injectAssociativeContext(currentMessages, skillEval, s.cfg.SawtoothEnabled)\n\t\t\tneedsReserialization = true\n\t\t\ts.logger.Printf(\"%s[req %d %s tid=%s] skill-eval injected (mode=%s)%s\", colorBlue, reqIdx, proj, threadID, s.cfg.SkillEvalInject, colorReset)\n\t\t}\n\t\tneedsReserialization = true\n\t\ts.logger.Printf(\"%s[req %d %s tid=%s] skill-eval injected%s\", colorBlue, reqIdx, proj, threadID, colorReset)\n\t}\n\n\t// === Collect injected learning IDs for next turn's inline reflection ===\n\tif threadID != \"\" {\n\t\tcurrentIDs := make(map[int64]string) // id → source\n\t\t// Source 1: Briefing learnings (cached, same every turn)\n\t\ts.briefingMu.RLock()\n\t\tbriefingSnapshot := s.briefingText\n\t\ts.briefingMu.RUnlock()\n\t\tif briefingSnapshot != \"\" {\n\t\t\tfor _, m := range idPattern.FindAllStringSubmatch(briefingSnapshot, -1) {\n\t\t\t\tif id, err := strconv.ParseInt(m[1], 10, 64); err == nil {\n\t\t\t\t\tcurrentIDs[id] = \"briefing\"\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\t// Source 2: Associative context (contextual, per-turn)\n\t\tif assocContext != \"\" {\n\t\t\tfor _, m := range idPattern.FindAllStringSubmatch(assocContext, -1) {\n\t\t\t\tif id, err := strconv.ParseInt(m[1], 10, 64); err == nil {\n\t\t\t\t\tcurrentIDs[id] = \"associative\" // overwrites briefing if both — associative is the stronger signal\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\t// Source 3: Fresh remember items (just learned)\n\t\tfor _, item := range remItems {\n\t\t\tif item.ID > 0 {\n\t\t\t\tcurrentIDs[item.ID] = \"fresh\"\n\t\t\t}\n\t\t}\n\t\ts.lastInjectedIDsMu.Lock()\n\t\tif len(currentIDs) > 0 {\n\t\t\t// Accumulate IDs across turns — don't overwrite.\n\t\t\t// Claude may reference an ID from turn 30 in turn 50.\n\t\t\texisting := s.lastInjectedIDs[threadID]\n\t\t\tif existing == nil {\n\t\t\t\texisting = make(map[int64]string)\n\t\t\t}\n\t\t\tfor id, source := range currentIDs {\n\t\t\t\texisting[id] = source\n\t\t\t}\n\t\t\ts.lastInjectedIDs[threadID] = existing\n\t\t}\n\t\ts.lastInjectedIDsMu.Unlock()\n\t}\n\n\t// Think reminder injection — only for real user turns, not tool continuations\n\tif threadID != \"\" {\n\t\tcurrentMessages, _ := req[\"messages\"].([]any)\n\t\tif currentMessages == nil {\n\t\t\tcurrentMessages = messages\n\t\t}\n\t\tif lastUserHasText(currentMessages) {\n\t\t\tthinkReminder := s.buildThinkReminder(threadID, threadID, false)\n\t\t\tif thinkReminder != \"\" {\n\t\t\t\treq[\"messages\"] = injectAssociativeContext(currentMessages, thinkReminder, s.cfg.SawtoothEnabled)\n\t\t\t\tneedsReserialization = true\n\t\t\t}\n\t\t}\n\n\t\t// Briefing injection: prepend user/assistant turn pair at beginning of messages.\n\t\t// Static per session → stable prefix → cacheable. Must be before dialog injection.\n\t\tif s.injectBriefingTurn(req, reqIdx, proj, threadID) {\n\t\t\tneedsReserialization = true\n\t\t}\n\n\t\t// Code Map injection: insert after briefing turn pair.\n\t\tif s.injectCodeMapTurn(req, reqIdx, proj, threadID) {\n\t\t\tneedsReserialization = true\n\t\t}\n\n\t\t// Dialog injection: append at end of messages with pseudo-padding for alternation.\n\t\t// Always works regardless of tool_result chains or conversation state.\n\t\tdr := s.checkDialogMessages(threadID, proj)\n\t\tif dr.Extra != \"\" {\n\t\t\t// Prepend session ID for agent-to-agent communication\n\t\t\tdr.Extra = fmt.Sprintf(\"DEINE_SESSION_ID: %s\\n\", threadID) + dr.Extra\n\t\t\tinjected := false\n\t\t\tif msgs, ok := req[\"messages\"].([]any); ok && len(msgs) > 0 {\n\t\t\t\t// Pad with empty assistant if last message is user (maintains alternation)\n\t\t\t\tlastMsg, _ := msgs[len(msgs)-1].(map[string]any)\n\t\t\t\tif lastMsg != nil && lastMsg[\"role\"] == \"user\" {\n\t\t\t\t\tmsgs = append(msgs, map[string]any{\n\t\t\t\t\t\t\"role\": \"assistant\",\n\t\t\t\t\t\t\"content\": \"​\", // zero-width space — invisible padding\n\t\t\t\t\t})\n\t\t\t\t}\n\t\t\t\t// Append dialog as new user message (becomes the last message Claude sees)\n\t\t\t\tmsgs = append(msgs, map[string]any{\n\t\t\t\t\t\"role\": \"user\",\n\t\t\t\t\t\"content\": \"\\n\" + dr.Extra + \"\\n\",\n\t\t\t\t})\n\t\t\t\treq[\"messages\"] = msgs\n\t\t\t\tneedsReserialization = true\n\t\t\t\tinjected = true\n\t\t\t}\n\t\t\t// Mark as read after 3 injection turns — long enough for Claude to process,\n\t\t\t// resilient against fast ping-pong sessions (time-based was broken for <30s turns).\n\t\t\tif injected && dr.HasUnread && dr.SessionID != \"\" {\n\t\t\t\tif s.shouldMarkChannelRead(dr) {\n\t\t\t\t\ts.markDialogRead(dr)\n\t\t\t\t}\n\t\t\t}\n\t\t\ts.logger.Printf(\"[dialog] injection injected=%v extra_len=%d\", injected, len(dr.Extra))\n\t\t}\n\t}\n\n\t// Signal tools are NOT injected into the main request.\n\t// They are collected via a separate async reflection call after the response.\n\t// See fireReflectionCall() in reflection.go.\n\t// NEVER put _signal_* tools back here — the model ignores them among 44+ real tools.\n\n\t// Legacy assistant annotation: timestamp on last assistant message after cache breakpoint.\n\t// User timestamps are now handled by TimestampStore (injected earlier in the pipeline).\n\tif threadID != \"\" {\n\t\tif msgs, ok := req[\"messages\"].([]any); ok && len(msgs) > 0 {\n\t\t\t// Find last cache_control breakpoint in messages\n\t\t\tcacheIdx := -1\n\t\t\tfor i, m := range msgs {\n\t\t\t\tmsg, _ := m.(map[string]any)\n\t\t\t\tif msg == nil {\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\t\t\t\tif _, has := msg[\"cache_control\"]; has {\n\t\t\t\t\tcacheIdx = i\n\t\t\t\t}\n\t\t\t\tif content, ok := msg[\"content\"].([]any); ok {\n\t\t\t\t\tfor _, block := range content {\n\t\t\t\t\t\tif b, ok := block.(map[string]any); ok {\n\t\t\t\t\t\t\tif _, has := b[\"cache_control\"]; has {\n\t\t\t\t\t\t\t\tcacheIdx = i\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t\tstartIdx := cacheIdx + 1\n\t\t\tif startIdx < 0 {\n\t\t\t\tstartIdx = 0\n\t\t\t}\n\t\t\ts.responseTsMu.RLock()\n\t\t\trespTime, hasRespTime := s.responseTimes[threadID]\n\t\t\ts.responseTsMu.RUnlock()\n\t\t\tif hasRespTime {\n\t\t\t\tfor i := len(msgs) - 1; i >= startIdx; i-- {\n\t\t\t\t\tmsg, _ := msgs[i].(map[string]any)\n\t\t\t\t\tif msg != nil && msg[\"role\"] == \"assistant\" {\n\t\t\t\t\t\tmeta := fmt.Sprintf(\"[%s] [msg:%d]\", respTime.Format(\"2006-01-02 15:04:05\"), i)\n\t\t\t\t\t\tprependMeta(msg, meta)\n\t\t\t\t\t\tneedsReserialization = true\n\t\t\t\t\t\tbreak\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\t// Self-Priming: DISABLED (experimental, see signal_handlers.go comment block).\n\t// The reflection-based selfprime (Haiku) was too inaccurate to be useful —\n\t// the main model adapts to user context organically from message history.\n\t// Kept for reference; re-enable by uncommenting below + setting ShouldActivate=true.\n\t//\n\t// s.selfPrimeMu.RLock()\n\t// anchor := s.selfPrimes[threadID]\n\t// s.selfPrimeMu.RUnlock()\n\t// if anchor != \"\" {\n\t// \tif msgs, ok := req[\"messages\"].([]any); ok && len(msgs) > 0 {\n\t// \t\tlastMsg, _ := msgs[len(msgs)-1].(map[string]any)\n\t// \t\tif lastMsg != nil && lastMsg[\"role\"] == \"user\" {\n\t// \t\t\tprimeBlock := map[string]any{\n\t// \t\t\t\t\"type\": \"text\",\n\t// \t\t\t\t\"text\": \"[self-prime]\\n[Self-Prime von deiner letzten Antwort]: \" + anchor,\n\t// \t\t\t}\n\t// \t\t\tif content, ok := lastMsg[\"content\"].([]any); ok {\n\t// \t\t\t\tlastMsg[\"content\"] = append(content, primeBlock)\n\t// \t\t\t} else if text, ok := lastMsg[\"content\"].(string); ok {\n\t// \t\t\t\tlastMsg[\"content\"] = []any{\n\t// \t\t\t\t\tmap[string]any{\"type\": \"text\", \"text\": text},\n\t// \t\t\t\t\tprimeBlock,\n\t// \t\t\t\t}\n\t// \t\t\t}\n\t// \t\t\tneedsReserialization = true\n\t// \t\t}\n\t// \t}\n\t// }\n\n\t// Prompt Caching: inject cache_control breakpoints on system + tools\n\t// Sawtooth: disabled (all 4 slots are used by CC system (2) + briefing (1) + CC messages (1))\n\t// Legacy: count existing breakpoints and fill up remaining slots.\n\tif !s.cfg.SawtoothEnabled && s.cacheGate.ShouldCache() {\n\t\tif n := InjectCacheBreakpoints(req, s.logger); n > 0 {\n\t\t\tneedsReserialization = true\n\t\t\ts.logger.Printf(\"[req %d %s] prompt cache: %d breakpoints injected\", reqIdx, proj, n)\n\t\t} else {\n\t\t\ts.logger.Printf(\"[req %d %s] prompt cache: no budget (4/4 already set)\", reqIdx, proj)\n\t\t}\n\t}\n\n\t// Final: upgrade cache TTL + enforce breakpoint limit (must run AFTER all injections + InjectCacheBreakpoints)\n\t// Shift the last messages-breakpoint from unstable user message to stable assistant message.\n\t// Text user messages change between turns (timestamps, reminders, structure); assistant messages don't.\n\t// Tool-result messages are excluded — they are stable cache anchors.\n\tif ShiftMessageBreakpoint(req) {\n\t\tneedsReserialization = true\n\t}\n\t// TTL upgrade only for API key auth — subscription (OAuth) users should not get extended TTL.\n\tif s.cfg.CacheTTL != \"\" && s.cfg.CacheTTL != \"ephemeral\" && IsAPIKeyAuth(r.Header) {\n\t\tif n := UpgradeCacheTTL(req, s.cfg.CacheTTL); n > 0 {\n\t\t\tneedsReserialization = true\n\t\t\ts.logger.Printf(\"[req %d %s] cache TTL upgraded: %d blocks → %s\", reqIdx, proj, n, s.cfg.CacheTTL)\n\t\t}\n\t}\n\tif n := EnforceCacheBreakpointLimit(req, maxCacheBreakpoints); n > 0 {\n\t\tneedsReserialization = true\n\t\ts.logger.Printf(\"[req %d %s] prompt cache: trimmed %d surplus breakpoints\", reqIdx, proj, n)\n\t}\n\n\t// Pre-flight: validate tool_use/tool_result pairing before forwarding.\n\t// Repairs orphans created by Stubify/Collapse/Injection pipeline.\n\tif msgs, ok := req[\"messages\"].([]any); ok {\n\t\trepaired, orphans := validateToolPairs(msgs, s.logger)\n\t\tif orphans > 0 {\n\t\t\treq[\"messages\"] = repaired\n\t\t\ts.logger.Printf(\"%s[req %d %s tid=%s] validate: repaired %d orphan tool_result(s)%s\", colorOrange, reqIdx, proj, threadID, orphans, colorReset)\n\t\t\tneedsReserialization = true\n\t\t}\n\t}\n\n\t// Early-out: skip re-serialization when nothing changed\n\tif !needsReserialization {\n\t\ts.logger.Printf(\"%s[req %d %s tid=%s] %d messages, ~%dk tokens (no stubbing, threshold=%dk)%s\", colorDim, reqIdx, proj, threadID, len(messages), totalTokens/1000, s.effectiveTokenThreshold(model)/1000, colorReset)\n\t\ts.forwardWithAnnotation(w, r, body, reqIdx, toolUseIDs, proj, threadID, msgCount, totalTokens)\n\t\treturn\n\t}\n\n\t// Re-serialize\n\tbody, err = json.Marshal(req)\n\tif err != nil {\n\t\ts.logger.Printf(\"%s[req %d %s tid=%s] re-marshal error: %v%s\", colorRed, reqIdx, proj, threadID, err, colorReset)\n\t\thttp.Error(w, \"internal proxy error\", http.StatusInternalServerError)\n\t\treturn\n\t}\n\n\t// Debug: dump final request body to file (enable with YESMEM_PROXY_DEBUG=1)\n\tif needsReserialization && os.Getenv(\"YESMEM_PROXY_DEBUG\") == \"1\" {\n\t\tdumpDir := filepath.Join(s.cfg.DataDir, \"debug\")\n\t\tos.MkdirAll(dumpDir, 0755)\n\t\tts := time.Now().Format(\"20060102-150405\")\n\t\tdumpPath := fmt.Sprintf(\"%s/req_%s_%d.json\", dumpDir, ts, reqIdx)\n\t\tos.WriteFile(dumpPath, body, 0644)\n\t\ts.logger.Printf(\"[req %d] dumped %dk to %s\", reqIdx, len(body)/1024, dumpPath)\n\t}\n\n\t// Task #7: Mark this request fingerprint for retry detection\n\tif !isRetryReq {\n\t\ts.markRequest(fp)\n\t}\n\n\t// Forward to Anthropic with SSE annotation extraction\n\t// Use original msgCount (pre-injection) so actual-based estimation stays consistent across turns\n\t// Use actual-based totalTokens as estimate when available (more accurate than full recount)\n\tfinalEstimate := totalTokens\n\tif s.sawtoothTrigger == nil || s.sawtoothTrigger.GetLastTokens(threadID) == 0 {\n\t\t// First request: no actual yet, do a full recount of post-injection messages\n\t\tfinalMsgs, _ := req[\"messages\"].([]any)\n\t\tfinalEstimate = s.measureOverhead(req) + s.countMessageTokens(finalMsgs)\n\t}\n\ts.logger.Printf(\"[req %d %s tid=%s] FINAL: ~%dk estimated (after all injections)\", reqIdx, proj, threadID, finalEstimate/1000)\n\tif raw := s.getRawEstimate(threadID); raw > 0 {\n\t\ts.cacheStatusWriter.UpdateRawForThread(threadID, raw)\n\t}\n\ts.forwardWithAnnotation(w, r, body, reqIdx, toolUseIDs, proj, threadID, msgCount, finalEstimate)\n}\n\n// bytesPerToken is the approximation factor for token estimation.\n// Anthropic recommends ~3.5 chars/token; we use 3.6 for slight overestimation\n// (triggers stubbing a bit earlier = safer for context budget).\nconst bytesPerToken = 3.6\n\n// TokenEstimateFunc is the central token estimation function.\n// All stubbing/budget decisions use this. Receives extracted text content\n// (not JSON wire format) so the tokenizer counts actual content tokens.\ntype TokenEstimateFunc func(text string) int\n"}, "367262dd0fbead43cf401b5848d762f623a22d69": {"t": 1785759359.1872404, "n": 59940, "h": "b8ef87b0c65d616f", "c": "package proxy\n\nimport (\n\t\"context\"\n\t\"crypto/tls\"\n\t\"encoding/json\"\n\t\"fmt\"\n\t\"io\"\n\t\"log\"\n\t\"net\"\n\t\"net/http\"\n\t\"os\"\n\t\"os/signal\"\n\t\"path/filepath\"\n\t\"strconv\"\n\t\"strings\"\n\t\"sync\"\n\t\"sync/atomic\"\n\t\"syscall\"\n\t\"time\"\n\n\ttokenizer \"github.com/qhenkart/anthropic-tokenizer-go\"\n)\n\n// Config holds proxy configuration.\ntype Config struct {\n\tListenAddr string // e.g. \":9099\"\n\tTargetURL string // e.g. \"https://api.anthropic.com\"\n\tTokenThreshold int // trigger stubbing above this estimated token count\n\tTokenMinimumThreshold int // stub down to this floor\n\tTokenThresholds map[string]int // model-specific thresholds: {\"opus\": 180000, \"haiku\": 130000}\n\tKeepRecent int // number of messages to always keep unmodified\n\tDataDir string // yesmem data directory for DB access\n\tOpenAITargetURL string // upstream for OpenAI-format clients; if empty, uses TargetURL\n\n\t// Signal reflection\n\tSignalsEnabled bool // enable async signal reflection calls\n\tSignalsMode string // \"reflection\" = separate API call\n\tSignalsEveryNTurns int // every N end_turn responses\n\tSignalsModel string // full model ID, e.g. \"claude-sonnet-4-6\"\n\tAPIKey string // needed for reflection API calls\n\n\t// Sawtooth cache optimization\n\tSawtoothEnabled bool // use sawtooth instead of progressive decay (default: true)\n\tCacheTTL string // \"ephemeral\" (5m) or \"1h\" (extended)\n\n\t// Usage deflation: scale down input_tokens reported to CC to suppress \"Context low\" warning.\n\t// 0 = disabled, 0.7 = report 70% of actual tokens. Real values kept for internal tracking.\n\tUsageDeflationFactor float64\n\n\t// System prompt rewriting\n\tPromptUngate bool // strip \"may or may not be relevant\" disclaimer from CLAUDE.md injection\n\tPromptRewrite bool // strip \"Output efficiency\" + \"short and concise\", inject Ant-quality directives\n\tPromptEnhance bool // CLAUDE.md authority reinforcement, comment discipline, persona-based tone\n\tEffortFloor string // minimum effort level: \"low\", \"medium\", \"high\", \"max\" (empty = off)\n\n\t// Cache keepalive\n\tCacheKeepaliveEnabled bool\n\tCacheKeepaliveMode string // \"auto\", \"5m\", \"1h\"\n\tCacheKeepalivePings5m int\n\tCacheKeepalivePings1h int\n\n\t// Forked agents\n\tForkedAgentsEnabled bool\n\tForkedAgentsModel string // full model ID\n\tForkedAgentsTokenGrowthTrigger int\n\tForkedAgentsMaxFailures int\n\tForkedAgentsMaxForksPerSession int\n\tForkedAgentsDebug bool\n\n\t// Cache state management\n\tResetCache bool // clear persisted frozen stubs and decay state on startup\n}\n\nconst maxAnnotations = 5000 // evict oldest when exceeded\n\n// ANSI color codes for log output\nconst (\n\tcolorReset = \"\\033[0m\"\n\tcolorDim = \"\\033[2m\" // gray — passthrough / no stubbing\n\tcolorGreen = \"\\033[32m\" // stubbed / compressed\n\tcolorLightGreen = \"\\033[92m\" // FINAL without compression\n\tcolorBlue = \"\\033[34m\" // injection\n\tcolorOrange = \"\\033[33m\" // skip / retry\n\tcolorYellow = \"\\033[33m\" // warnings\n\tcolorRed = \"\\033[31m\" // errors\n)\n\n// Server is the infinite-thread proxy.\ntype Server struct {\n\tcfg Config\n\thttpClient *http.Client\n\tlogger *log.Logger\n\trequestIdx atomic.Int64\n\n\t// Post-hoc annotations: tool_use_id → first ~120 chars of Claude's response\n\tmu sync.RWMutex\n\tannotations map[string]string\n\n\t// Progressive decay tracker\n\tdecay *DecayTracker\n\n\t// Living narrative block\n\tnarrative *Narrative\n\n\t// Pivot moments cache (from daemon)\n\tpivotMu sync.RWMutex\n\tpivotTexts []string\n\tpivotCached time.Time\n\n\t// Aggregate stats (Task #2)\n\tstats *ProxyStats\n\n\t// Local tokenizer for accurate token counting\n\ttokenizer *tokenizer.Tokenizer\n\n\t// Cached overhead tokens (system + tools), set on first request per thread\n\toverheadMu sync.RWMutex\n\toverheadTokens int // unused, kept for interface compat\n\n\t// Hysteresis state for stubbing (Task #6)\n\tstubActive atomic.Bool\n\n\t// Daemon connection state — first successful connect flips to true\n\tdaemonReady atomic.Bool\n\n\t// Retry detection (Task #7)\n\tretryMu sync.RWMutex\n\tlastFingerprint string\n\n\t// Session start time for temporal annotation in archive blocks\n\tsessionStartMu sync.RWMutex\n\tsessionStartTime time.Time\n\n\t// Runtime config override (set via MCP set_config tool)\n\tconfigOverrideMu sync.RWMutex\n\ttokenThresholdOverrides map[string]int // model-key → threshold, \"\" = global fallback\n\n\t// Briefing cache — loaded once per thread, injected as user/assistant message pair\n\tbriefingMu sync.RWMutex\n\tbriefingText string\n\tcodeMapText string\n\n\t// Cognitive signal bus — routes _signal_* tool calls to handlers\n\tsignalBus *SignalBus\n\n\t// Inline reflection: track which learning IDs were injected per thread\n\tlastInjectedIDsMu sync.Mutex\n\tlastInjectedIDs map[string]map[int64]string // threadID → id → source (\"briefing\"|\"associative\"|\"fresh\")\n\tsessionInjectCounts map[string]map[int64]int // threadID → learningID → injection count this session\n\tlastTurnInjected map[string]map[int64]bool // threadID → learningIDs injected in previous turn\n\n\t// Self-priming anchors: threadID → last self-prime text\n\tselfPrimeMu sync.RWMutex\n\tselfPrimes map[string]string\n\n\t// Timestamp tracking: threadID → time of last response completion\n\tresponseTsMu sync.RWMutex\n\tresponseTimes map[string]time.Time\n\n\t// Think reminder: per-thread request counter (replaces hook-think file-based counter)\n\tthinkMu sync.Mutex\n\tthinkCounters map[string]int // threadID → request count\n\n\tchannelMu sync.Mutex\n\tchannelInjectCount map[string]int // sessionID → injection turn count\n\n\t// Prompt cache gating — enables cache_control breakpoints when requests are frequent\n\tcacheGate *CacheGate\n\n\t// Sawtooth cache optimization\n\tfrozenStubs *FrozenStubs\n\tsawtoothTrigger *SawtoothTrigger\n\ttimestampStore *TimestampStore\n\tcacheStatusWriter *CacheStatusWriter\n\tcacheKeepalive *CacheKeepalive\n\tcacheTTLDetector *CacheTTLDetector\n\n\t// Cumulative token savings from collapsing (per threadID)\n\trawSavingsMu sync.Mutex\n\trawSavings map[string]int\n\n\t// Skill hint tracking: per-thread active skills\n\tskillTracker *skillHintTracker\n\n\t// Forked agent state: per-thread token growth + failure tracking\n\tforkState *ForkState\n\tforkConfigs []ForkConfig\n\n\t// Rules re-injection: condensed CLAUDE.md rules injected every ~40k tokens\n\trulesMu sync.RWMutex\n\trulesBlock string // cached condensed rules (fetched once from daemon)\n\trulesTokenCount map[string]int // threadID → tokens since last rules injection\n\trulesCollapseInjected map[string]bool // threadID → true if collapse already injected rules (reset by normal inject)\n\tmsgCounters *msgCounters // global per-thread msg counter, persists across collapses\n\n\t// Loop detection: per-thread warning state\n\tloopMu sync.Mutex\n\tloopStates map[string]*LoopState // threadID → state\n}\n\n// Run starts the proxy server and blocks until interrupted.\nfunc Run(cfg Config) error {\n\ts := &Server{\n\t\tcfg: cfg,\n\t\thttpClient: &http.Client{\n\t\t\tTimeout: 10 * time.Minute,\n\t\t\tTransport: &http.Transport{\n\t\t\t\tTLSClientConfig: &tls.Config{},\n\t\t\t\tMaxIdleConns: 10,\n\t\t\t\tIdleConnTimeout: 90 * time.Second,\n\t\t\t\tDisableCompression: true,\n\t\t\t\tResponseHeaderTimeout: 60 * time.Second,\n\t\t\t},\n\t\t},\n\t\tlogger: createLogger(cfg.DataDir),\n\t\tannotations: make(map[string]string),\n\t\tdecay: NewDecayTracker(),\n\t\tnarrative: NewNarrative(),\n\t\tstats: &ProxyStats{startTime: time.Now()},\n\t\tselfPrimes: make(map[string]string),\n\t\tlastInjectedIDs: make(map[string]map[int64]string),\n\t\tsessionInjectCounts: make(map[string]map[int64]int),\n\t\tlastTurnInjected: make(map[string]map[int64]bool),\n\t\tresponseTimes: make(map[string]time.Time),\n\t\tthinkCounters: make(map[string]int),\n\t\tchannelInjectCount: make(map[string]int),\n\t\tcacheGate: NewCacheGate(cacheGapForTTL(cfg.CacheTTL)),\n\t\tfrozenStubs: NewFrozenStubsWithTTL(sawtoothTTLForCacheTTL(cfg.CacheTTL)),\n\t\ttimestampStore: NewTimestampStore(),\n\t\tsawtoothTrigger: NewSawtoothTrigger(cacheGapForTTL(cfg.CacheTTL), cfg.TokenThreshold, cfg.TokenMinimumThreshold),\n\t\tcacheStatusWriter: NewCacheStatusWriter(cfg.DataDir, cfg.CacheTTL, cfg.TokenThreshold, cfg.TokenMinimumThreshold),\n\t\tcacheTTLDetector: NewCacheTTLDetectorWithPersist(cfg.DataDir),\n\t\tskillTracker: newSkillHintTracker(),\n\t\trulesTokenCount: make(map[string]int),\n\t\trulesCollapseInjected: make(map[string]bool),\n\t\tmsgCounters: newMsgCounters(),\n\t\tloopStates: make(map[string]*LoopState),\n\t\tforkState: NewForkState(cfg.ForkedAgentsTokenGrowthTrigger, cfg.ForkedAgentsMaxFailures, cfg.ForkedAgentsMaxForksPerSession),\n\t\tforkConfigs: []ForkConfig{},\n\t}\n\n\t// Log persisted detection state\n\tif sup := s.cacheTTLDetector.Is1hSupported(); sup != nil {\n\t\ts.logger.Printf(\"[proxy] loaded persisted TTL detection: 1h_supported=%v\", *sup)\n\t\tif *sup && s.frozenStubs != nil {\n\t\t\ts.frozenStubs.UpdateTTL(sawtoothTTLForCacheTTL(\"1h\"))\n\t\t}\n\t}\n\n\tif cfg.CacheKeepaliveEnabled {\n\t\ts.cacheKeepalive = NewCacheKeepalive(CacheKeepaliveConfig{\n\t\t\tTarget: cfg.TargetURL,\n\t\t\tMode: cfg.CacheKeepaliveMode,\n\t\t\tPings5m: cfg.CacheKeepalivePings5m,\n\t\t\tPings1h: cfg.CacheKeepalivePings1h,\n\t\t\tDetector: s.cacheTTLDetector,\n\t\t\tLogger: s.logger,\n\t\t\tOnPing: func(threadID string, cacheRead, cacheWrite int) {\n\t\t\t\ts.cacheTTLDetector.RecordResponse(cacheRead, cacheWrite, 0)\n\t\t\t\tif s.frozenStubs != nil {\n\t\t\t\t\ts.frozenStubs.Touch(threadID)\n\t\t\t\t}\n\t\t\t\tif s.sawtoothTrigger != nil {\n\t\t\t\t\ts.sawtoothTrigger.TouchRequestTime(threadID)\n\t\t\t\t}\n\t\t\t},\n\t\t})\n\t}\n\n\t// Wire detector + keepalive to status writer for terminal display\n\ts.cacheStatusWriter.SetDetector(s.cacheTTLDetector)\n\tif s.cacheKeepalive != nil {\n\t\ts.cacheStatusWriter.SetKeepalive(s.cacheKeepalive)\n\t}\n\n\t// Register forked agent configs\n\tif cfg.ForkedAgentsEnabled {\n\t\ts.forkConfigs = append(s.forkConfigs, NewExtractAndEvaluateConfig(cfg.ForkedAgentsModel))\n\t\tmodelDesc := cfg.ForkedAgentsModel\n\t\tif modelDesc == \"\" {\n\t\t\tmodelDesc = \"(same as main thread)\"\n\t\t}\n\t\ts.logger.Printf(\"[proxy] forked agents enabled: %d configs, model=%s, trigger=%dk tokens\",\n\t\t\tlen(s.forkConfigs), modelDesc, cfg.ForkedAgentsTokenGrowthTrigger/1000)\n\t}\n\n\t// Wire sawtooth token persistence via daemon RPC\n\ts.sawtoothTrigger.SetPersistFunc(func(key, value string) {\n\t\tif _, err := s.queryDaemon(\"set_proxy_state\", map[string]any{\"key\": key, \"value\": value}); err != nil {\n\t\t\ts.logger.Printf(\"[sawtooth] persist failed for %s: %v\", key, err)\n\t\t}\n\t})\n\ts.sawtoothTrigger.SetLoadFunc(func(key string) (string, bool) {\n\t\tresult, err := s.queryDaemon(\"get_proxy_state\", map[string]any{\"key\": key})\n\t\tif err != nil || result == nil {\n\t\t\treturn \"\", false\n\t\t}\n\t\tvar resp struct {\n\t\t\tValue string `json:\"value\"`\n\t\t}\n\t\tif json.Unmarshal(result, &resp) != nil || resp.Value == \"\" {\n\t\t\treturn \"\", false\n\t\t}\n\t\treturn resp.Value, true\n\t})\n\n\t// Wire frozen stubs persistence via same daemon RPC\n\ts.frozenStubs.SetPersistFunc(func(key, value string) {\n\t\tif _, err := s.queryDaemon(\"set_proxy_state\", map[string]any{\"key\": key, \"value\": value}); err != nil {\n\t\t\ts.logger.Printf(\"[frozen] persist failed for %s: %v\", key, err)\n\t\t}\n\t})\n\ts.frozenStubs.SetLoadFunc(func(key string) (string, bool) {\n\t\tresult, err := s.queryDaemon(\"get_proxy_state\", map[string]any{\"key\": key})\n\t\tif err != nil || result == nil {\n\t\t\treturn \"\", false\n\t\t}\n\t\tvar resp struct {\n\t\t\tValue string `json:\"value\"`\n\t\t}\n\t\tif json.Unmarshal(result, &resp) != nil || resp.Value == \"\" {\n\t\t\treturn \"\", false\n\t\t}\n\t\treturn resp.Value, true\n\t})\n\n\t// Wire decay tracker persistence via same daemon RPC\n\ts.decay.SetPersistFunc(func(key, value string) {\n\t\tif _, err := s.queryDaemon(\"set_proxy_state\", map[string]any{\"key\": key, \"value\": value}); err != nil {\n\t\t\ts.logger.Printf(\"[decay] persist failed for %s: %v\", key, err)\n\t\t}\n\t})\n\ts.decay.SetLoadFunc(func(key string) (string, bool) {\n\t\tresult, err := s.queryDaemon(\"get_proxy_state\", map[string]any{\"key\": key})\n\t\tif err != nil || result == nil {\n\t\t\treturn \"\", false\n\t\t}\n\t\tvar resp struct {\n\t\t\tValue string `json:\"value\"`\n\t\t}\n\t\tif json.Unmarshal(result, &resp) != nil || resp.Value == \"\" {\n\t\t\treturn \"\", false\n\t\t}\n\t\treturn resp.Value, true\n\t})\n\n\t// Wire timestamp store persistence via same daemon RPC\n\ts.timestampStore.SetPersistFunc(func(key, value string) {\n\t\tif _, err := s.queryDaemon(\"set_proxy_state\", map[string]any{\"key\": key, \"value\": value}); err != nil {\n\t\t\ts.logger.Printf(\"[timestamps] persist failed for %s: %v\", key, err)\n\t\t}\n\t})\n\ts.timestampStore.SetLoadFunc(func(key string) (string, bool) {\n\t\tresult, err := s.queryDaemon(\"get_proxy_state\", map[string]any{\"key\": key})\n\t\tif err != nil || result == nil {\n\t\t\treturn \"\", false\n\t\t}\n\t\tvar resp struct {\n\t\t\tValue string `json:\"value\"`\n\t\t}\n\t\tif json.Unmarshal(result, &resp) != nil || resp.Value == \"\" {\n\t\t\treturn \"\", false\n\t\t}\n\t\treturn resp.Value, true\n\t})\n\n\t// Clear persisted cache state if --reset-cache was passed\n\tif cfg.ResetCache {\n\t\tfor _, prefix := range []string{\"frozen:\", \"decay:\", \"timestamps:\"} {\n\t\t\tresult, err := s.queryDaemon(\"delete_proxy_state_prefix\", map[string]any{\"prefix\": prefix})\n\t\t\tif err != nil {\n\t\t\t\ts.logger.Printf(\"[reset-cache] failed to delete %s*: %v\", prefix, err)\n\t\t\t} else {\n\t\t\t\tvar resp struct{ Deleted int }\n\t\t\t\tjson.Unmarshal(result, &resp)\n\t\t\t\ts.logger.Printf(\"[reset-cache] deleted %d %s* entries\", resp.Deleted, prefix)\n\t\t\t}\n\t\t}\n\t}\n\n\t// Init signal bus with daemon RPC as callback\n\ts.signalBus = NewSignalBus(s.logger)\n\tregisterSignalHandlers(s.signalBus, s.logger, s.queryDaemon, func(threadID, anchor string) {\n\t\ts.selfPrimeMu.Lock()\n\t\ts.selfPrimes[threadID] = anchor\n\t\ts.selfPrimeMu.Unlock()\n\t})\n\n\t// Init local tokenizer (BPE-based, ~60ms)\n\ttok, err := tokenizer.New()\n\tif err != nil {\n\t\ts.logger.Printf(\"%s[proxy] tokenizer init failed, falling back to heuristic: %v%s\", colorOrange, err, colorReset)\n\t} else {\n\t\ts.tokenizer = tok\n\t\ts.logger.Printf(\"[proxy] tokenizer initialized\")\n\t}\n\n\tmux := http.NewServeMux()\n\tmux.HandleFunc(\"/health\", s.handleHealth)\n\tmux.HandleFunc(\"/\", s.handleRequest)\n\n\tsrv := &http.Server{\n\t\tAddr: sanitizeListenAddr(cfg.ListenAddr),\n\t\tHandler: mux,\n\t}\n\n\tdone := make(chan os.Signal, 1)\n\tsignal.Notify(done, syscall.SIGINT, syscall.SIGTERM)\n\n\tgo func() {\n\t\t<-done\n\t\ts.logger.Println(\"shutting down (draining in-flight requests)...\")\n\t\tif s.cacheKeepalive != nil {\n\t\t\ts.cacheKeepalive.Stop()\n\t\t}\n\t\tctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)\n\t\tdefer cancel()\n\t\tif err := srv.Shutdown(ctx); err != nil {\n\t\t\ts.logger.Printf(\"graceful shutdown failed: %v, forcing close\", err)\n\t\t\tsrv.Close()\n\t\t}\n\t}()\n\n\ts.logger.Printf(\"listening on %s → %s (threshold=%d, keepRecent=%d, model_thresholds=%v)\",\n\t\tsanitizeListenAddr(cfg.ListenAddr), cfg.TargetURL, cfg.TokenThreshold, cfg.KeepRecent, cfg.TokenThresholds)\n\n\tif err := srv.ListenAndServe(); err != http.ErrServerClosed {\n\t\treturn fmt.Errorf(\"listen: %w\", err)\n\t}\n\treturn nil\n}\n\n// effectiveTokenThreshold returns the runtime token threshold for a given model.\n// Priority: MCP override (model-specific) > MCP override (global) > config model-specific > config global.\nfunc (s *Server) effectiveTokenThreshold(model string) int {\n\ts.configOverrideMu.RLock()\n\toverrides := s.tokenThresholdOverrides\n\ts.configOverrideMu.RUnlock()\n\tif len(overrides) > 0 && model != \"\" {\n\t\tlower := strings.ToLower(model)\n\t\tfor key, threshold := range overrides {\n\t\t\tif key != \"\" && strings.Contains(lower, strings.ToLower(key)) && threshold > 0 {\n\t\t\t\treturn threshold\n\t\t\t}\n\t\t}\n\t}\n\t// Global override fallback (key \"\")\n\tif len(overrides) > 0 {\n\t\tif globalOverride, ok := overrides[\"\"]; ok && globalOverride > 0 {\n\t\t\treturn globalOverride\n\t\t}\n\t}\n\tif model != \"\" && len(s.cfg.TokenThresholds) > 0 {\n\t\tlower := strings.ToLower(model)\n\t\tfor key, threshold := range s.cfg.TokenThresholds {\n\t\t\tif strings.Contains(lower, strings.ToLower(key)) {\n\t\t\t\treturn threshold\n\t\t\t}\n\t\t}\n\t}\n\treturn s.cfg.TokenThreshold\n}\n\n// SetTokenThresholdOverride sets a runtime override for the token threshold per model key.\n// Pass threshold=0 to reset the override for that key. Key \"\" is the global fallback.\nfunc (s *Server) SetTokenThresholdOverride(modelKey string, threshold int) {\n\ts.configOverrideMu.Lock()\n\tif s.tokenThresholdOverrides == nil {\n\t\ts.tokenThresholdOverrides = make(map[string]int)\n\t}\n\tif threshold <= 0 {\n\t\tdelete(s.tokenThresholdOverrides, modelKey)\n\t} else {\n\t\ts.tokenThresholdOverrides[modelKey] = threshold\n\t}\n\ts.configOverrideMu.Unlock()\n\n\t// Propagate global threshold to sawtooth trigger so runtime overrides take effect\n\tif modelKey == \"\" && s.sawtoothTrigger != nil {\n\t\tif threshold > 0 {\n\t\t\ts.sawtoothTrigger.SetTokenThreshold(threshold)\n\t\t} else {\n\t\t\ts.sawtoothTrigger.SetTokenThreshold(s.cfg.TokenThreshold)\n\t\t}\n\t}\n}\n\n// refreshConfigOverrides loads runtime config overrides from daemon proxy_state.\n// Checks session-specific override first (by threadID), then falls back to global.\nfunc (s *Server) refreshConfigOverrides(threadID string) {\n\t// Try session-specific first\n\tif threadID != \"\" {\n\t\tresult, err := s.queryDaemon(\"get_proxy_state\", map[string]any{\"key\": \"config_override:token_threshold:\" + threadID})\n\t\tif err == nil && result != nil {\n\t\t\tvar resp struct {\n\t\t\t\tValue string `json:\"value\"`\n\t\t\t}\n\t\t\tif json.Unmarshal(result, &resp) == nil && resp.Value != \"\" {\n\t\t\t\tif v, err := strconv.Atoi(resp.Value); err == nil && v > 0 {\n\t\t\t\t\ts.SetTokenThresholdOverride(\"\", v)\n\t\t\t\t\treturn\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\t// Fall back to global\n\tresult, err := s.queryDaemon(\"get_proxy_state\", map[string]any{\"key\": \"config_override:token_threshold\"})\n\tif err != nil || result == nil {\n\t\treturn\n\t}\n\tvar resp struct {\n\t\tValue string `json:\"value\"`\n\t}\n\tif json.Unmarshal(result, &resp) == nil && resp.Value != \"\" {\n\t\tif v, err := strconv.Atoi(resp.Value); err == nil && v > 0 {\n\t\t\ts.SetTokenThresholdOverride(\"\", v)\n\t\t}\n\t}\n}\n\n// queryDaemon opens a fresh unix socket connection per call (thread-safe, no import cycle).\n// ensureFrozenPersisted checks if frozen stubs for a thread are in the DB.\n// If missing (e.g. daemon was unreachable after deploy), re-persists them.\n// Runs asynchronously to avoid blocking the request path.\nfunc (s *Server) ensureFrozenPersisted(threadID string) {\n\tgo func() {\n\t\tkey := \"frozen:\" + threadID\n\t\tresult, err := s.queryDaemon(\"get_proxy_state\", map[string]any{\"key\": key})\n\t\tif err != nil {\n\t\t\treturn // daemon still unreachable, will retry next request\n\t\t}\n\t\tvar resp struct{ Value string }\n\t\tif json.Unmarshal(result, &resp) == nil && resp.Value != \"\" {\n\t\t\treturn // already persisted\n\t\t}\n\t\t// Not in DB — trigger re-persist from in-memory state\n\t\ts.frozenStubs.Persist(threadID)\n\t\ts.decay.Persist(threadID)\n\t\ts.logger.Printf(\"[frozen] re-persisted stubs for %s (was missing from DB)\", threadID)\n\t}()\n}\n\nfunc (s *Server) queryDaemon(method string, params map[string]any) (json.RawMessage, error) {\n\tif s.cfg.DataDir == \"\" {\n\t\treturn nil, fmt.Errorf(\"no data dir configured\")\n\t}\n\tsockPath := filepath.Join(s.cfg.DataDir, \"daemon.sock\")\n\n\t// On cold start (daemon not yet confirmed ready), retry longer to survive daemon restart\n\tmaxAttempts := 3\n\tif !s.daemonReady.Load() {\n\t\tmaxAttempts = 30 // 30 x 500ms = 15s max wait for daemon startup\n\t}\n\n\tvar conn net.Conn\n\tvar err error\n\tfor attempt := 0; attempt < maxAttempts; attempt++ {\n\t\tconn, err = net.DialTimeout(\"unix\", sockPath, 3*time.Second)\n\t\tif err == nil {\n\t\t\ts.daemonReady.Store(true)\n\t\t\tbreak\n\t\t}\n\t\tif attempt < maxAttempts-1 {\n\t\t\ttime.Sleep(500 * time.Millisecond)\n\t\t}\n\t}\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"dial daemon: %w\", err)\n\t}\n\tdefer conn.Close()\n\t// Briefing generation can take 10-20s (LLM refinement + scanner);\n\t// other RPCs (search, learnings) are fast and fine with 5s.\n\tdeadline := 5 * time.Second\n\tif method == \"generate_briefing\" {\n\t\tdeadline = 30 * time.Second\n\t}\n\tconn.SetDeadline(time.Now().Add(deadline))\n\n\t// JSON-RPC style request/response (matches daemon.SocketServer protocol)\n\ttype request struct {\n\t\tMethod string `json:\"method\"`\n\t\tParams map[string]any `json:\"params,omitempty\"`\n\t}\n\ttype response struct {\n\t\tResult json.RawMessage `json:\"result,omitempty\"`\n\t\tError string `json:\"error,omitempty\"`\n\t}\n\n\tif err := json.NewEncoder(conn).Encode(request{Method: method, Params: params}); err != nil {\n\t\treturn nil, fmt.Errorf(\"send: %w\", err)\n\t}\n\n\tvar resp response\n\tif err := json.NewDecoder(conn).Decode(&resp); err != nil {\n\t\treturn nil, fmt.Errorf(\"recv: %w\", err)\n\t}\n\tif resp.Error != \"\" {\n\t\treturn nil, fmt.Errorf(\"daemon: %s\", resp.Error)\n\t}\n\treturn resp.Result, nil\n}\n\nfunc (s *Server) handleRequest(w http.ResponseWriter, r *http.Request) {\n\tif isResponsesPath(r) {\n\t\ts.handleResponses(w, r)\n\t\treturn\n\t}\n\tif isOpenAIPath(r) {\n\t\ts.handleOpenAICompletions(w, r)\n\t\treturn\n\t}\n\tif r.Method == \"POST\" && strings.HasPrefix(r.URL.Path, \"/v1/messages\") &&\n\t\t!strings.Contains(r.URL.Path, \"/batches\") &&\n\t\t!strings.Contains(r.URL.Path, \"/count_tokens\") {\n\t\ts.handleMessages(w, r)\n\t\treturn\n\t}\n\ts.passthrough(w, r)\n}\n\nfunc (s *Server) handleMessages(w http.ResponseWriter, r *http.Request) {\n\treqIdx := int(s.requestIdx.Add(1))\n\n\t// Track session start time (first request sets it)\n\ts.sessionStartMu.Lock()\n\tif s.sessionStartTime.IsZero() {\n\t\ts.sessionStartTime = time.Now()\n\t}\n\ts.sessionStartMu.Unlock()\n\n\t// Sawtooth: periodic TTL eviction of frozen stubs\n\tif s.cfg.SawtoothEnabled && reqIdx%50 == 0 {\n\t\tif n := s.frozenStubs.Evict(); n > 0 {\n\t\t\ts.logger.Printf(\"[req %d] SAWTOOTH: evicted %d stale frozen stubs\", reqIdx, n)\n\t\t}\n\t}\n\n\t// Task #9: Bypass — skip all proxy logic\n\tif isBypassed(r.Header) {\n\t\ts.logger.Printf(\"%s[req %d] bypass active, passthrough%s\", colorDim, reqIdx, colorReset)\n\t\tbody, err := io.ReadAll(r.Body)\n\t\tr.Body.Close()\n\t\tif err != nil {\n\t\t\thttp.Error(w, \"failed to read request body\", http.StatusBadGateway)\n\t\t\treturn\n\t\t}\n\t\ts.forwardRaw(w, r, body)\n\t\treturn\n\t}\n\n\tbody, err := io.ReadAll(r.Body)\n\tr.Body.Close()\n\tif err != nil {\n\t\ts.logger.Printf(\"[req %d] read body error: %v\", reqIdx, err)\n\t\thttp.Error(w, \"failed to read request body\", http.StatusBadGateway)\n\t\treturn\n\t}\n\n\t// Parse JSON\n\tvar req map[string]any\n\tif err := json.Unmarshal(body, &req); err != nil {\n\t\ts.logger.Printf(\"%s[req %d] JSON parse error, passing through: %v%s\", colorDim, reqIdx, err, colorReset)\n\t\ts.forwardRaw(w, r, body)\n\t\treturn\n\t}\n\n\tmessages, ok := req[\"messages\"].([]any)\n\tif !ok {\n\t\ts.forwardRaw(w, r, body)\n\t\treturn\n\t}\n\n\treq[\"messages\"] = messages\n\n\t// Count ALL raw messages BEFORE sawtooth — this gives the correct total msg:N\n\t// for the entire session (CC sends all messages, proxy compacts later).\n\trawMsgCount := len(messages)\n\n\t// Use session_id directly as thread ID (unique per CC session).\n\t// Prefer X-Claude-Code-Session-Id header (CC v2.1.86+), fallback to body metadata.\n\tthreadID := extractSessionID(req, r.Header.Get(\"X-Claude-Code-Session-Id\"))\n\tif threadID == \"\" {\n\t\tthreadID = DeriveThreadID(req)\n\t}\n\tproj := extractProjectName(req)\n\tmodel, _ := req[\"model\"].(string)\n\n\t// Refresh runtime config overrides from daemon (per-session or global)\n\ts.refreshConfigOverrides(threadID)\n\n\t// Track whether req has been modified and needs re-serialization\n\tneedsReserialization := false\n\n\t// Message count at request entry (used for actual-based token estimation)\n\tmsgCount := len(messages)\n\n\t// Log message count + cache breakpoint positions for diagnostics\n\ts.logger.Printf(\"req %d: %d messages total\", reqIdx, msgCount)\n\tlogCacheBreakpointLocations(req, s.logger)\n\n\t// Pre-modification dump: capture the raw request before any proxy changes.\n\tif os.Getenv(\"YESMEM_PROXY_DEBUG\") == \"1\" {\n\t\tif preBody, err := json.Marshal(req); err == nil {\n\t\t\tdumpDir := filepath.Join(s.cfg.DataDir, \"debug\")\n\t\t\tos.MkdirAll(dumpDir, 0755)\n\t\t\tts := time.Now().Format(\"20060102-150405\")\n\t\t\tdumpPath := fmt.Sprintf(\"%s/req_%s_%d_pre.json\", dumpDir, ts, reqIdx)\n\t\t\tos.WriteFile(dumpPath, preBody, 0644)\n\t\t\ts.logger.Printf(\"[req %d] pre-dump %dk to %s\", reqIdx, len(preBody)/1024, dumpPath)\n\t\t}\n\t}\n\n\t// Cache bug mitigations (CC Bun fork issues):\n\t// Bug 1: Normalize cch= sentinel patterns in message content to prevent\n\t// Bun's string replacement from corrupting the wrong occurrence.\n\tif SanitizeBillingSentinel(messages) {\n\t\ts.logger.Printf(\"[req %d] CACHE: sanitized billing sentinel in message content\", reqIdx)\n\t\tneedsReserialization = true\n\t}\n\t// Bug 2: Normalize system[0] billing hash to a fixed value so the cache\n\t// prefix stays stable across fresh/resume session boundaries.\n\tif NormalizeBillingHeader(req) {\n\t\ts.logger.Printf(\"[req %d] CACHE: normalized billing header cch= hash\", reqIdx)\n\t\tneedsReserialization = true\n\t}\n\n\t// prompt_ungate: strip the CLAUDE.md subordination disclaimer so user instructions carry full authority.\n\tif s.cfg.PromptUngate {\n\t\tif StripCLAUDEMDDisclaimer(req) {\n\t\t\ts.logger.Printf(\"[req %d] SYSTEM: stripped CLAUDE.md disclaimer\", reqIdx)\n\t\t\tneedsReserialization = true\n\t\t}\n\t}\n\n\t// prompt_rewrite: strip output-throttling directives, rewrite quality caps, inject Ant-quality directives\n\tif s.cfg.PromptRewrite {\n\t\t// StripOutputEfficiency: disabled — Anthropic replaced # Output efficiency with # Text output (CC ~2.1.117)\n\t\t// if StripOutputEfficiency(req) {\n\t\t// \ts.logger.Printf(\"[req %d] REWRITE: stripped Output efficiency section\", reqIdx)\n\t\t// \tneedsReserialization = true\n\t\t// }\n\t\tif StripToneBrevity(req) {\n\t\t\ts.logger.Printf(\"[req %d] REWRITE: stripped 'short and concise' from Tone\", reqIdx)\n\t\t\tneedsReserialization = true\n\t\t}\n\t\tif RewriteGoldPlating(req) {\n\t\t\ts.logger.Printf(\"[req %d] REWRITE: rewritten gold-plating directive\", reqIdx)\n\t\t\tneedsReserialization = true\n\t\t}\n\t\tif RewriteErrorHandling(req) {\n\t\t\ts.logger.Printf(\"[req %d] REWRITE: rewritten error handling directive\", reqIdx)\n\t\t\tneedsReserialization = true\n\t\t}\n\t\tif RewriteThreeLinesRule(req) {\n\t\t\ts.logger.Printf(\"[req %d] REWRITE: rewritten three-lines rule\", reqIdx)\n\t\t\tneedsReserialization = true\n\t\t}\n\t\tif RewriteSubagentCompleteness(req) {\n\t\t\ts.logger.Printf(\"[req %d] REWRITE: rewritten subagent completeness\", reqIdx)\n\t\t\tneedsReserialization = true\n\t\t}\n\t\tif RewriteExploreAgentSpeed(req) {\n\t\t\ts.logger.Printf(\"[req %d] REWRITE: rewritten explore agent speed bias\", reqIdx)\n\t\t\tneedsReserialization = true\n\t\t}\n\t\tif RewriteSubagentCodeSuppression(req) {\n\t\t\ts.logger.Printf(\"[req %d] REWRITE: rewritten subagent code suppression\", reqIdx)\n\t\t\tneedsReserialization = true\n\t\t}\n\t\tif RewriteScopeMatching(req) {\n\t\t\ts.logger.Printf(\"[req %d] REWRITE: rewritten scope matching\", reqIdx)\n\t\t\tneedsReserialization = true\n\t\t}\n\t\tInjectAntDirectives(req)\n\t\ts.logger.Printf(\"[req %d] REWRITE: injected Ant-quality directives\", reqIdx)\n\t\tneedsReserialization = true\n\t}\n\n\t// prompt_enhance: CLAUDE.md authority + comment discipline + persona tone\n\tif s.cfg.PromptEnhance {\n\t\tInjectCLAUDEMDAuthority(req)\n\t\tif raw, err := s.queryDaemon(\"get_persona\", nil); err == nil {\n\t\t\tvar persona map[string]any\n\t\t\tif json.Unmarshal(raw, &persona) == nil {\n\t\t\t\tif traits, ok := persona[\"traits\"].(map[string]any); ok {\n\t\t\t\t\tif verbosity, ok := traits[\"verbosity\"].(string); ok {\n\t\t\t\t\t\tInjectPersonaTone(req, verbosity)\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\ts.logger.Printf(\"[req %d] ENHANCE: injected authority + persona tone\", reqIdx)\n\t\tneedsReserialization = true\n\t}\n\n\t// thinking normalization: convert thinking.type \"enabled\" → \"adaptive\" for models that require it\n\tif NormalizeThinkingType(req) {\n\t\ts.logger.Printf(\"[req %d] THINKING: normalized type=enabled → adaptive for %s\", reqIdx, model)\n\t\tneedsReserialization = true\n\t}\n\n\t// effort_floor: ensure output_config.effort is at least the configured minimum\n\tif s.cfg.EffortFloor != \"\" {\n\t\tif EnforceEffortFloor(req, s.cfg.EffortFloor) {\n\t\t\ts.logger.Printf(\"[req %d] EFFORT: enforced floor=%s\", reqIdx, s.cfg.EffortFloor)\n\t\t\tneedsReserialization = true\n\t\t}\n\t}\n\n\t// Inject identity hint on first message of a thread so Claude knows the briefing is from itself.\n\tif len(messages) <= 1 {\n\t\tAppendSystemBlock(req, \"yesmem-identity\", \"The first user message contains your briefing — written by yourself, from your memory. This is not an external document. This is you, remembering.\")\n\t\tneedsReserialization = true\n\t}\n\n\t// Token estimation: actual-based (lastActual + delta) or full count fallback\n\toverhead := s.measureOverhead(req)\n\ttotalTokens := s.estimateTotalTokens(threadID, messages, overhead)\n\n\t// Task #7: Retry detection — skip side-effects on retries\n\tfp := requestFingerprint(messages)\n\tisRetryReq := s.isRetry(fp)\n\tif isRetryReq {\n\t\ts.logger.Printf(\"%s[req %d %s tid=%s] retry detected, skipping side-effects%s\", colorOrange, reqIdx, proj, threadID, colorReset)\n\t}\n\n\t// Increment project turn counter (async, non-blocking, skip on retries)\n\tif !isRetryReq {\n\t\tturnProject := proj\n\t\tif turnProject == \"\" {\n\t\t\tturnProject = \"__global__\"\n\t\t}\n\t\tgo s.queryDaemon(\"increment_turn\", map[string]any{\"project\": turnProject})\n\t}\n\n\t// Extract user query once (used by re-expansion and associative retrieval)\n\tuserQuery := lastUserText(messages)\n\n\t// Extract tool_use_ids for post-hoc annotation (passed as parameter, no shared state)\n\ttoolUseIDs := extractToolUseIDs(messages)\n\n\t// === Inline Reflection: scan previous assistant response for signals ===\n\ts.lastInjectedIDsMu.Lock()\n\tprevInjected := s.lastInjectedIDs[threadID]\n\ts.lastInjectedIDsMu.Unlock()\n\n\tif len(prevInjected) > 0 && !isRetryReq {\n\t\tsignals := scanAssistantSignals(messages)\n\n\t\t// Signal 1: Learning Used — use_count (all sources)\n\t\tvar confirmedUsed []int64\n\t\tfor _, id := range signals.UsedIDs {\n\t\t\tif _, ok := prevInjected[id]; ok {\n\t\t\t\tconfirmedUsed = append(confirmedUsed, id)\n\t\t\t}\n\t\t}\n\t\tif len(confirmedUsed) > 0 {\n\t\t\tidsAny := make([]any, len(confirmedUsed))\n\t\t\tfor i, id := range confirmedUsed {\n\t\t\t\tidsAny[i] = id\n\t\t\t}\n\t\t\tgo s.queryDaemon(\"increment_use\", map[string]any{\"ids\": idsAny})\n\t\t}\n\n\t\t// Signal 2: Noise — only for associative and fresh injections.\n\t\t// Briefing learnings are injected every turn, so \"not used this turn\"\n\t\t// is expected and not noise. Only contextual injections that were\n\t\t// ignored are real noise signals.\n\t\tusedSet := make(map[int64]bool, len(confirmedUsed))\n\t\tfor _, id := range confirmedUsed {\n\t\t\tusedSet[id] = true\n\t\t}\n\t\tvar noiseIDs []int64\n\t\tfor id, source := range prevInjected {\n\t\t\tif usedSet[id] {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tif source == \"associative\" || source == \"fresh\" {\n\t\t\t\tnoiseIDs = append(noiseIDs, id)\n\t\t\t}\n\t\t\t// briefing: skip — not being used in one turn is expected\n\t\t}\n\t\tif len(noiseIDs) > 0 {\n\t\t\tidsAny := make([]any, len(noiseIDs))\n\t\t\tfor i, id := range noiseIDs {\n\t\t\t\tidsAny[i] = id\n\t\t\t}\n\t\t\tgo s.queryDaemon(\"increment_noise\", map[string]any{\"ids\": idsAny})\n\t\t}\n\n\t\t// Signal 3: Error Feedback — fail_count on used learnings when tool errors occurred.\n\t\t// If Claude referenced a learning and the resulting action still failed,\n\t\t// the learning didn't prevent the error. Only bump on USED ids (not all injected).\n\t\tif signals.HasToolErrors && len(confirmedUsed) > 0 {\n\t\t\tfailAny := make([]any, len(confirmedUsed))\n\t\t\tfor i, id := range confirmedUsed {\n\t\t\t\tfailAny[i] = id\n\t\t\t}\n\t\t\tgo s.queryDaemon(\"increment_fail\", map[string]any{\"ids\": failAny})\n\t\t\tif s.logger != nil {\n\t\t\t\ts.logger.Printf(\"[inline-reflection] error feedback: %d used learnings → fail_count++\", len(confirmedUsed))\n\t\t\t}\n\t\t}\n\n\t\t// Signal 4: Knowledge Gaps\n\t\tfor _, topic := range signals.GapTopics {\n\t\t\tgo s.queryDaemon(\"track_gap\", map[string]any{\"topic\": topic, \"project\": proj})\n\t\t}\n\n\t\t// Signal 3: Contradictions\n\t\tfor _, desc := range signals.Contradictions {\n\t\t\tgo s.queryDaemon(\"flag_contradiction\", map[string]any{\"description\": desc, \"project\": proj, \"thread_id\": threadID})\n\t\t}\n\n\t\tif len(confirmedUsed) > 0 || len(signals.GapTopics) > 0 || len(signals.Contradictions) > 0 {\n\t\t\ts.logger.Printf(\"[req %d %s tid=%s] inline-reflection: injected=%d used=%d noise=%d gaps=%d contradictions=%d\",\n\t\t\t\treqIdx, proj, threadID, len(prevInjected), len(confirmedUsed), len(noiseIDs), len(signals.GapTopics), len(signals.Contradictions))\n\t\t}\n\t}\n\n\t// Commit detection: check for git commit in tool_result, trigger async stale-learning evaluation\n\tif ci := detectGitCommit(messages); ci != nil {\n\t\tworkdir := extractWorkingDirectory(req)\n\t\ts.logger.Printf(\"[req %d %s tid=%s] git commit detected: %s %s\", reqIdx, proj, threadID, ci.Hash, ci.Message)\n\t\tgo s.queryDaemon(\"invalidate_on_commit\", map[string]any{\n\t\t\t\"hash\": ci.Hash, \"project\": proj, \"workdir\": workdir,\n\t\t})\n\t}\n\n\t// Loop detection: check for repeating tool-call patterns and inject warning\n\tif threadID != \"\" && !isRetryReq {\n\t\ts.loopMu.Lock()\n\t\tloopState := s.loopStates[threadID]\n\t\tif loopState == nil {\n\t\t\tloopState = &LoopState{}\n\t\t\ts.loopStates[threadID] = loopState\n\t\t}\n\t\ts.loopMu.Unlock()\n\n\t\tif warning, level := CheckLoopAndFormat(messages, loopState); warning != \"\" {\n\t\t\tAppendSystemBlock(req, \"yesmem-loop-warning\", warning)\n\t\t\tneedsReserialization = true\n\t\t\ts.logger.Printf(\"%s[req %d %s tid=%s] LOOP: level %d warning injected%s\", colorOrange, reqIdx, proj, threadID, level, colorReset)\n\t\t}\n\t}\n\n\t// Task #4: Subagent detection — skip all proxy logic for short conversations\n\tif isSubagent(messages, req) {\n\t\t// Inject docs hint for subagents if reference docs are indexed\n\t\tif hint := s.getDocsHint(proj); hint != \"\" {\n\t\t\tmodified := injectDocsHintForSubagent(messages, hint)\n\t\t\treq[\"messages\"] = modified\n\t\t\tif newBody, err := json.Marshal(req); err == nil {\n\t\t\t\tbody = newBody\n\t\t\t}\n\t\t\ts.logger.Printf(\"%s[req %d %s tid=%s] %d messages (subagent, docs-hint injected)%s\", colorOrange, reqIdx, proj, threadID, len(messages), colorReset)\n\t\t} else {\n\t\t\ts.logger.Printf(\"%s[req %d %s tid=%s] %d messages (subagent, passthrough)%s\", colorOrange, reqIdx, proj, threadID, len(messages), colorReset)\n\t\t}\n\t\ts.forwardWithAnnotation(w, r, body, reqIdx, toolUseIDs, proj, threadID, msgCount)\n\t\treturn\n\t}\n\n\t// === Sawtooth Cache Optimization ===\n\t// When enabled: use frozen stubs between stub-cycles for cache hits.\n\t// When disabled: fall back to progressive decay on every request above threshold.\n\n\tsawtoothCutoff := 0 // raw index where fresh tail starts (0 = no frozen stubs)\n\tsawtoothStubs := 0 // number of frozen stubs in combined array\n\n\tif s.cfg.SawtoothEnabled {\n\t\t// Restore decay state from DB on cold start (before frozen stubs use it)\n\t\ts.decay.LoadFromDB(threadID)\n\n\t\t// Check for existing frozen stubs first\n\t\tfrozen := s.frozenStubs.Get(threadID, messages)\n\t\tif frozen != nil {\n\t\t\tsawtoothCutoff = frozen.Cutoff\n\t\t\tsawtoothStubs = len(frozen.Messages)\n\t\t\t// Estimate combined token count: frozen prefix + fresh tail + overhead\n\t\t\tfreshMessages := messages[frozen.Cutoff:]\n\t\t\tfreshTokens := s.countMessageTokens(freshMessages)\n\t\t\tcombinedTokens := frozen.Tokens + freshTokens + overhead\n\n\t\t\tif combinedTokens > s.effectiveTokenThreshold(model) ||\n\t\t\t\ts.sawtoothTrigger.GetLastTokens(threadID) > s.effectiveTokenThreshold(model) {\n\t\t\t\t// Fresh tail grew too large — invalidate and re-stub\n\t\t\t\ts.logger.Printf(\"[req %d %s tid=%s] SAWTOOTH: frozen prefix expired (%dk frozen + %dk fresh + %dk overhead = %dk > %dk threshold)\",\n\t\t\t\t\treqIdx, proj, threadID, frozen.Tokens/1000, freshTokens/1000, overhead/1000, combinedTokens/1000, s.effectiveTokenThreshold(model)/1000)\n\t\t\t\ts.frozenStubs.Invalidate(threadID)\n\t\t\t\tfrozen = nil // fall through to trigger check below\n\t\t\t} else {\n\t\t\t\t// Use frozen prefix + fresh tail\n\t\t\t\tcombined := make([]any, 0, len(frozen.Messages)+len(freshMessages))\n\t\t\t\tcombined = append(combined, frozen.Messages...)\n\t\t\t\tcombined = append(combined, freshMessages...)\n\t\t\t\t// Eager-stub large tool_results in fresh tail (model already processed them)\n\t\t\t\tbeforeEager := s.countMessageTokens(combined[len(frozen.Messages):])\n\t\t\t\tcombined = EagerStubToolResults(combined, len(frozen.Messages), s.countTokens)\n\t\t\t\tafterEager := s.countMessageTokens(combined[len(frozen.Messages):])\n\t\t\t\tif beforeEager != afterEager {\n\t\t\t\t\ts.logger.Printf(\"[req %d %s tid=%s] EAGER-STUB: fresh %dk → %dk (saved %dk)\", reqIdx, proj, threadID, beforeEager/1000, afterEager/1000, (beforeEager-afterEager)/1000)\n\t\t\t\t}\n\t\t\t\treq[\"messages\"] = combined\n\t\t\t\tneedsReserialization = true\n\t\t\t\t// Strip embedded CC breakpoints from frozen stubs — they waste cache slots.\n\t\t\t\t// InjectFrozenStubCacheBreakpoint adds exactly one below.\n\t\t\t\tif n := StripMessagesCacheControl(req, 0, len(frozen.Messages)); n > 0 {\n\t\t\t\t\ts.logger.Printf(\"[req %d %s tid=%s] SAWTOOTH: stripped %d embedded breakpoints from frozen stubs\", reqIdx, proj, threadID, n)\n\t\t\t\t}\n\t\t\t\ts.logger.Printf(\"%s[req %d %s tid=%s] SAWTOOTH: frozen prefix (%d stubs, %dk msg) + %d fresh (%dk msg) + %dk overhead = %dk total%s\",\n\t\t\t\t\tcolorGreen, reqIdx, proj, threadID, len(frozen.Messages), frozen.Tokens/1000, len(freshMessages), freshTokens/1000, overhead/1000, combinedTokens/1000, colorReset)\n\t\t\t\trawMsgTokens := s.countMessageTokens(messages)\n\t\t\t\ts.setRawEstimate(threadID, rawMsgTokens+overhead)\n\t\t\t\ts.logger.Printf(\"[req %d %s tid=%s] RAW ESTIMATE: msgTokens=%dk + overhead=%dk = %dk (messages=%d)\", reqIdx, proj, threadID, rawMsgTokens/1000, overhead/1000, (rawMsgTokens+overhead)/1000, len(messages))\n\t\t\t\tif InjectFrozenStubCacheBreakpoint(req, len(frozen.Messages)) {\n\t\t\t\t\ts.logger.Printf(\"[req %d %s tid=%s] SAWTOOTH: frozen stub cache breakpoint at messages[%d]\",\n\t\t\t\t\t\treqIdx, proj, threadID, len(frozen.Messages)-1)\n\t\t\t\t}\n\n\t\t\t\t// Ensure frozen stubs are persisted (may have failed on first request after deploy)\n\t\t\t\ts.ensureFrozenPersisted(threadID)\n\t\t\t}\n\t\t}\n\t\tif frozen == nil {\n\t\t\t// No frozen stubs — check trigger\n\t\t\ttriggerReason := s.sawtoothTrigger.ShouldTrigger(threadID, totalTokens)\n\t\t\tif triggerReason != TriggerNone {\n\t\t\t\ts.logger.Printf(\"[req %d %s tid=%s] SAWTOOTH stub-cycle triggered: %s (tokens=%dk)\",\n\t\t\t\t\treqIdx, proj, threadID, triggerReason, totalTokens/1000)\n\n\t\t\t\t// Refresh briefing during stub-cycle — disabled (briefing now via SessionStart hook)\n\t\t\t\t// s.refreshBriefing(proj)\n\t\t\t\t// if s.briefingText != \"\" {\n\t\t\t\t// \tUpsertSystemBlockCached(req, \"yesmem-briefing\", s.briefingText)\n\t\t\t\t// }\n\n\t\t\t\t// Run full stub pipeline\n\t\t\t\ts.setRawEstimate(threadID, totalTokens)\n\t\t\t\t_ = s.runStubCycle(messages, req, reqIdx, proj, threadID, overhead, totalTokens, userQuery, isRetryReq)\n\n\t\t\t\t// Freeze the result\n\t\t\t\tfinalMessages, _ := req[\"messages\"].([]any)\n\t\t\t\tif finalMessages != nil && len(finalMessages) > 0 {\n\t\t\t\t\t// Boundary = last message of original that maps to last stub\n\t\t\t\t\tcutoff := len(messages)\n\t\t\t\t\tsawtoothCutoff = cutoff\n\t\t\t\t\tvar boundaryMsg any\n\t\t\t\t\tif cutoff > 0 {\n\t\t\t\t\t\tboundaryMsg = messages[cutoff-1]\n\t\t\t\t\t}\n\t\t\t\t\tfrozenTokens := s.countMessageTokens(finalMessages)\n\t\t\t\t\ts.frozenStubs.Store(threadID, finalMessages, cutoff, boundaryMsg, frozenTokens, totalTokens)\n\t\t\t\t\ts.decay.Persist(threadID)\n\t\t\t\t\ts.logger.Printf(\"[req %d %s tid=%s] SAWTOOTH: frozen %d messages at cutoff=%d (~%dk tokens)\",\n\t\t\t\t\t\treqIdx, proj, threadID, len(finalMessages), cutoff, frozenTokens/1000)\n\t\t\t\t\tif InjectFrozenStubCacheBreakpoint(req, len(finalMessages)) {\n\t\t\t\t\t\ts.logger.Printf(\"[req %d %s tid=%s] SAWTOOTH: frozen stub cache breakpoint at messages[%d]\",\n\t\t\t\t\t\t\treqIdx, proj, threadID, len(finalMessages)-1)\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tneedsReserialization = true\n\t\t\t} else {\n\t\t\t\t// No trigger, no frozen stubs — eager-stub to delay first collapse\n\t\t\t\tbeforeEager := s.countMessageTokens(messages)\n\t\t\t\tmessages = EagerStubToolResults(messages, 0, s.countTokens)\n\t\t\t\tafterEager := s.countMessageTokens(messages)\n\t\t\t\tif beforeEager != afterEager {\n\t\t\t\t\ts.logger.Printf(\"[req %d %s tid=%s] EAGER-STUB: %dk → %dk (saved %dk)\", reqIdx, proj, threadID, beforeEager/1000, afterEager/1000, (beforeEager-afterEager)/1000)\n\t\t\t\t\treq[\"messages\"] = messages\n\t\t\t\t\tneedsReserialization = true\n\t\t\t\t}\n\t\t\t\ts.logger.Printf(\"%s[req %d %s tid=%s] %d messages, ~%dk tokens (sawtooth: no trigger)%s\",\n\t\t\t\t\tcolorDim, reqIdx, proj, threadID, len(messages), totalTokens/1000, colorReset)\n\t\t\t}\n\t\t}\n\t} else {\n\t\t// Legacy path: progressive decay\n\t\tif s.shouldStub(totalTokens, model) {\n\t\t\ts.setRawEstimate(threadID, totalTokens)\n\t\t\t_ = s.runStubCycle(messages, req, reqIdx, proj, threadID, overhead, totalTokens, userQuery, isRetryReq)\n\t\t\tneedsReserialization = true\n\t\t}\n\t}\n\n\t// Persistent timestamp injection: re-injects stored timestamps on ALL previous user messages.\n\t// msg:N is computed from position (counting user messages in raw array), NOT from a counter.\n\t// The injected data is deterministic (same bytes every turn) = cache-safe after initial miss.\n\tif threadID != \"\" {\n\t\tif currentMsgs, ok := req[\"messages\"].([]any); ok && len(currentMsgs) > 0 {\n\t\t\ts.timestampStore.Load(threadID)\n\n\t\t\t// Offset = cutoff - stubsCount: maps combined array index to raw position.\n\t\t\t// Raw position of message at combined index I = cutoff + (I - stubs).\n\t\t\t// msgN = rawPos + 1 = cutoff + (I - stubs) + 1 = (cutoff - stubs) + I + 1.\n\t\t\tmsgOffset := sawtoothCutoff - sawtoothStubs\n\n\t\t\ts.logger.Printf(\"[req %d %s] timestamps: rawTotal=%d, postSawtooth=%d, cutoff=%d, stubs=%d, offset=%d\", reqIdx, proj, rawMsgCount, len(currentMsgs), sawtoothCutoff, sawtoothStubs, msgOffset)\n\n\t\t\t// Pre-store assistant timestamp BEFORE inject loop so it's available immediately\n\t\t\ts.responseTsMu.RLock()\n\t\t\trespTime, hasRespTime := s.responseTimes[threadID]\n\t\t\ts.responseTsMu.RUnlock()\n\t\t\tif hasRespTime {\n\t\t\t\trespStr := shortWeekday(respTime.Weekday()) + \" \" + respTime.Format(\"2006-01-02 15:04:05\")\n\t\t\t\tfor j := len(currentMsgs) - 2; j >= 0; j-- {\n\t\t\t\t\tprevMsg, _ := currentMsgs[j].(map[string]any)\n\t\t\t\t\tif prevMsg == nil || prevMsg[\"role\"] != \"assistant\" {\n\t\t\t\t\t\tcontinue\n\t\t\t\t\t}\n\t\t\t\t\tif !msgHasTextBlock(prevMsg) {\n\t\t\t\t\t\ts.logger.Printf(\"[req %d %s] timestamps: skip assistant[%d] no text block\", reqIdx, proj, j)\n\t\t\t\t\t\tcontinue // skip tool-use-only assistant messages\n\t\t\t\t\t}\n\t\t\t\t\tprevMsgN := msgOffset + j + 1\n\t\t\t\t\tif _, exists := s.timestampStore.Get(threadID, prevMsgN); !exists {\n\t\t\t\t\t\ts.timestampStore.Store(threadID, prevMsgN, &TimestampMeta{Timestamp: respStr})\n\t\t\t\t\t\ts.logger.Printf(\"[req %d %s] timestamps: stored assistant msg:%d ts=%s\", reqIdx, proj, prevMsgN, respStr)\n\t\t\t\t\t} else {\n\t\t\t\t\t\ts.logger.Printf(\"[req %d %s] timestamps: assistant msg:%d already has ts\", reqIdx, proj, prevMsgN)\n\t\t\t\t\t}\n\t\t\t\t\tbreak\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\ts.logger.Printf(\"[req %d %s] timestamps: no responseTimes for thread\", reqIdx, proj)\n\t\t\t}\n\n\t\t\t// Inject [msg:N] (+ timestamp/delta if stored) on all PREVIOUS messages\n\t\t\tif n := InjectTimestamps(s.timestampStore, threadID, currentMsgs, len(currentMsgs)-1, msgOffset, sawtoothStubs); n > 0 {\n\t\t\t\tneedsReserialization = true\n\t\t\t\ts.logger.Printf(\"[req %d %s] timestamps: injected %d\", reqIdx, proj, n)\n\t\t\t}\n\n\t\t\t// Current (last) user message: full timestamp + delta + msg:N from raw count\n\t\t\tlastMsg, _ := currentMsgs[len(currentMsgs)-1].(map[string]any)\n\t\t\tif lastMsg != nil && lastMsg[\"role\"] == \"user\" && !msgHasMetaPrefix(lastMsg) {\n\t\t\t\tnow := time.Now()\n\t\t\t\tnowStr := shortWeekday(now.Weekday()) + \" \" + now.Format(\"2006-01-02 15:04:05\")\n\t\t\t\tentry := &TimestampMeta{Timestamp: nowStr}\n\t\t\t\tif hasRespTime {\n\t\t\t\t\tentry.Delta = formatDelta(now.Sub(respTime))\n\t\t\t\t}\n\t\t\t\ts.timestampStore.Store(threadID, rawMsgCount, entry)\n\t\t\t\tprependMeta(lastMsg, BuildMeta(rawMsgCount, entry))\n\t\t\t\tneedsReserialization = true\n\t\t\t}\n\n\t\t\ts.timestampStore.Persist(threadID)\n\t\t}\n\t}\n\n\t// Associative context injection (for ALL requests, not just stubbed ones)\n\t// Re-enabled with higher thresholds (55/75, max 1) after disabling Fresh Remember.\n\tassocContext := s.findAssociativeContextFor(userQuery, proj, threadID, messages)\n\tif assocContext != \"\" {\n\t\tcurrentMessages, _ := req[\"messages\"].([]any)\n\t\tif currentMessages == nil {\n\t\t\tcurrentMessages = messages\n\t\t}\n\t\treq[\"messages\"] = injectAssociativeContext(currentMessages, assocContext, s.cfg.SawtoothEnabled)\n\t\tneedsReserialization = true\n\t\ts.logger.Printf(\"%s[req %d %s tid=%s] associative context injected%s\", colorBlue, reqIdx, proj, threadID, colorReset)\n\t}\n\n\t// Doc-chunk context injection: separate search path for indexed docs (not skills)\n\tdocContext := s.findDocContextFor(userQuery, proj, messages)\n\tif docContext != \"\" {\n\t\tcurrentMessages, _ := req[\"messages\"].([]any)\n\t\tif currentMessages == nil {\n\t\t\tcurrentMessages = messages\n\t\t}\n\t\treq[\"messages\"] = injectAssociativeContext(currentMessages, docContext, s.cfg.SawtoothEnabled)\n\t\tneedsReserialization = true\n\t\ts.logger.Printf(\"%s[req %d %s tid=%s] doc context injected%s\", colorBlue, reqIdx, proj, threadID, colorReset)\n\t}\n\n\t// Fresh Remember Injection: DISABLED\n\t// Causes echo-loop: Claude saves via remember(), sees it again next turn as\n\t// [yesmem fresh memory], reacts to own output instead of user's request.\n\t// Recovery after /clear is handled by recovery.go (briefing), not here.\n\t// Subagents get their own briefing. No remaining use-case.\n\t// remItems := s.popRecentRemember()\n\tvar remItems []recentLearningItem\n\n\t// Rules re-injection: inject condensed CLAUDE.md rules every ~40k output tokens\n\tif rulesBlock := s.rulesInject(threadID, totalTokens, proj); rulesBlock != \"\" {\n\t\tcurrentMessages, _ := req[\"messages\"].([]any)\n\t\tif currentMessages == nil {\n\t\t\tcurrentMessages = messages\n\t\t}\n\t\treq[\"messages\"] = injectAssociativeContext(currentMessages, s.formatRulesReminder(rulesBlock, proj), s.cfg.SawtoothEnabled)\n\t\tneedsReserialization = true\n\t\ts.logger.Printf(\"%s[req %d %s tid=%s] rules reminder injected%s\", colorBlue, reqIdx, proj, threadID, colorReset)\n\t}\n\n\t// Plan auto-activation: nudge Claude to call set_plan() when reading a plan file\n\tif planFile := detectPlanFileRead(messages); planFile != \"\" {\n\t\tif activePlan, _ := s.getActivePlan(threadID); shouldNudgePlan(planFile, activePlan) {\n\t\t\tAppendSystemBlock(req, \"plan-nudge\",\n\t\t\t\tfmt.Sprintf(\"Du hast einen Implementierungsplan gelesen (%s). Aktiviere ihn jetzt via set_plan() damit Plan-Checkpoints funktionieren und dein Fortschritt getrackt wird.\", filepath.Base(planFile)))\n\t\t\tneedsReserialization = true\n\t\t\ts.logger.Printf(\"%s[req %d %s tid=%s] plan nudge injected for %s%s\", colorBlue, reqIdx, proj, threadID, filepath.Base(planFile), colorReset)\n\t\t}\n\t}\n\n\t// Plan checkpoint: inject plan reminder every ~20k tokens if active plan exists\n\ts.detectPlanToolCall(messages, threadID, totalTokens)\n\tif checkpoint := s.planCheckpointInject(threadID, totalTokens); checkpoint != \"\" {\n\t\tcurrentMessages, _ := req[\"messages\"].([]any)\n\t\tif currentMessages == nil {\n\t\t\tcurrentMessages = messages\n\t\t}\n\t\treq[\"messages\"] = injectAssociativeContext(currentMessages, checkpoint, s.cfg.SawtoothEnabled)\n\t\tneedsReserialization = true\n\t\ts.logger.Printf(\"%s[req %d %s tid=%s] plan checkpoint injected%s\", colorBlue, reqIdx, proj, threadID, colorReset)\n\t}\n\n\t// Docs hint injection: inject docs-available reminder every ~10k tokens (independent of plan)\n\tif docsHint := s.docsHintInject(threadID, totalTokens, proj); docsHint != \"\" {\n\t\tcurrentMessages, _ := req[\"messages\"].([]any)\n\t\tif currentMessages == nil {\n\t\t\tcurrentMessages = messages\n\t\t}\n\t\treq[\"messages\"] = injectAssociativeContext(currentMessages, docsHint, s.cfg.SawtoothEnabled)\n\t\tneedsReserialization = true\n\t\ts.logger.Printf(\"%s[req %d %s tid=%s] docs hint injected%s\", colorBlue, reqIdx, proj, threadID, colorReset)\n\t}\n\tif len(remItems) > 0 {\n\t\tvar lines []string\n\t\tfor _, item := range remItems {\n\t\t\tif item.ID > 0 {\n\t\t\t\tlines = append(lines, fmt.Sprintf(\"- [ID:%d] %s\", item.ID, truncateStr(item.Text, 200)))\n\t\t\t} else {\n\t\t\t\tlines = append(lines, \"- \"+truncateStr(item.Text, 200))\n\t\t\t}\n\t\t}\n\t\tremContext := fmt.Sprintf(\"[yesmem fresh memory]\\nGerade gelernt:\\n%s\\n%s\\n[/yesmem fresh memory]\", strings.Join(lines, \"\\n\"), InlineReflectionHint)\n\t\tcurrentMessages, _ := req[\"messages\"].([]any)\n\t\tif currentMessages == nil {\n\t\t\tcurrentMessages = messages\n\t\t}\n\t\treq[\"messages\"] = injectAssociativeContext(currentMessages, remContext, s.cfg.SawtoothEnabled)\n\t\tneedsReserialization = true\n\t\ts.logger.Printf(\"%s[req %d %s tid=%s] fresh remember injected (%d items)%s\", colorBlue, reqIdx, proj, threadID, len(remItems), colorReset)\n\t}\n\n\t// Skill eval injection: mandatory evaluation instruction for real user input only\n\tif threadID != \"\" && proj != \"\" && isUserInputTurn(messages) {\n\t\t// First: detect any skill activations from message history (updates tracker)\n\t\ts.syncSkillActivations(messages, proj, threadID)\n\n\t\t// Inject mandatory skill evaluation block into last user message\n\t\tskillEval := buildSkillEvalBlock()\n\t\tcurrentMessages, _ := req[\"messages\"].([]any)\n\t\tif currentMessages == nil {\n\t\t\tcurrentMessages = messages\n\t\t}\n\t\treq[\"messages\"] = injectAssociativeContext(currentMessages, skillEval, s.cfg.SawtoothEnabled)\n\t\tneedsReserialization = true\n\t\ts.logger.Printf(\"%s[req %d %s tid=%s] skill-eval injected%s\", colorBlue, reqIdx, proj, threadID, colorReset)\n\t}\n\n\t// === Collect injected learning IDs for next turn's inline reflection ===\n\tif threadID != \"\" {\n\t\tcurrentIDs := make(map[int64]string) // id → source\n\t\t// Source 1: Briefing learnings (cached, same every turn)\n\t\ts.briefingMu.RLock()\n\t\tbriefingSnapshot := s.briefingText\n\t\ts.briefingMu.RUnlock()\n\t\tif briefingSnapshot != \"\" {\n\t\t\tfor _, m := range idPattern.FindAllStringSubmatch(briefingSnapshot, -1) {\n\t\t\t\tif id, err := strconv.ParseInt(m[1], 10, 64); err == nil {\n\t\t\t\t\tcurrentIDs[id] = \"briefing\"\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\t// Source 2: Associative context (contextual, per-turn)\n\t\tif assocContext != \"\" {\n\t\t\tfor _, m := range idPattern.FindAllStringSubmatch(assocContext, -1) {\n\t\t\t\tif id, err := strconv.ParseInt(m[1], 10, 64); err == nil {\n\t\t\t\t\tcurrentIDs[id] = \"associative\" // overwrites briefing if both — associative is the stronger signal\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\t// Source 3: Fresh remember items (just learned)\n\t\tfor _, item := range remItems {\n\t\t\tif item.ID > 0 {\n\t\t\t\tcurrentIDs[item.ID] = \"fresh\"\n\t\t\t}\n\t\t}\n\t\ts.lastInjectedIDsMu.Lock()\n\t\tif len(currentIDs) > 0 {\n\t\t\t// Accumulate IDs across turns — don't overwrite.\n\t\t\t// Claude may reference an ID from turn 30 in turn 50.\n\t\t\texisting := s.lastInjectedIDs[threadID]\n\t\t\tif existing == nil {\n\t\t\t\texisting = make(map[int64]string)\n\t\t\t}\n\t\t\tfor id, source := range currentIDs {\n\t\t\t\texisting[id] = source\n\t\t\t}\n\t\t\ts.lastInjectedIDs[threadID] = existing\n\t\t}\n\t\ts.lastInjectedIDsMu.Unlock()\n\t}\n\n\t// Think reminder injection — only for real user turns, not tool continuations\n\tif threadID != \"\" {\n\t\tcurrentMessages, _ := req[\"messages\"].([]any)\n\t\tif currentMessages == nil {\n\t\t\tcurrentMessages = messages\n\t\t}\n\t\tif lastUserHasText(currentMessages) {\n\t\t\tthinkReminder := s.buildThinkReminder(threadID, threadID, false)\n\t\t\tif thinkReminder != \"\" {\n\t\t\t\treq[\"messages\"] = injectAssociativeContext(currentMessages, thinkReminder, s.cfg.SawtoothEnabled)\n\t\t\t\tneedsReserialization = true\n\t\t\t}\n\t\t}\n\n\t\t// Briefing injection: prepend user/assistant turn pair at beginning of messages.\n\t\t// Static per session → stable prefix → cacheable. Must be before dialog injection.\n\t\tif s.injectBriefingTurn(req, reqIdx, proj, threadID) {\n\t\t\tneedsReserialization = true\n\t\t}\n\n\t\t// Code Map injection: insert after briefing turn pair.\n\t\tif s.injectCodeMapTurn(req, reqIdx, proj, threadID) {\n\t\t\tneedsReserialization = true\n\t\t}\n\n\t\t// Dialog injection: append at end of messages with pseudo-padding for alternation.\n\t\t// Always works regardless of tool_result chains or conversation state.\n\t\tdr := s.checkDialogMessages(threadID, proj)\n\t\tif dr.Extra != \"\" {\n\t\t\t// Prepend session ID for agent-to-agent communication\n\t\t\tdr.Extra = fmt.Sprintf(\"DEINE_SESSION_ID: %s\\n\", threadID) + dr.Extra\n\t\t\tinjected := false\n\t\t\tif msgs, ok := req[\"messages\"].([]any); ok && len(msgs) > 0 {\n\t\t\t\t// Pad with empty assistant if last message is user (maintains alternation)\n\t\t\t\tlastMsg, _ := msgs[len(msgs)-1].(map[string]any)\n\t\t\t\tif lastMsg != nil && lastMsg[\"role\"] == \"user\" {\n\t\t\t\t\tmsgs = append(msgs, map[string]any{\n\t\t\t\t\t\t\"role\": \"assistant\",\n\t\t\t\t\t\t\"content\": \"​\", // zero-width space — invisible padding\n\t\t\t\t\t})\n\t\t\t\t}\n\t\t\t\t// Append dialog as new user message (becomes the last message Claude sees)\n\t\t\t\tmsgs = append(msgs, map[string]any{\n\t\t\t\t\t\"role\": \"user\",\n\t\t\t\t\t\"content\": \"\\n\" + dr.Extra + \"\\n\",\n\t\t\t\t})\n\t\t\t\treq[\"messages\"] = msgs\n\t\t\t\tneedsReserialization = true\n\t\t\t\tinjected = true\n\t\t\t}\n\t\t\t// Mark as read after 3 injection turns — long enough for Claude to process,\n\t\t\t// resilient against fast ping-pong sessions (time-based was broken for <30s turns).\n\t\t\tif injected && dr.HasUnread && dr.SessionID != \"\" {\n\t\t\t\tif s.shouldMarkChannelRead(dr) {\n\t\t\t\t\ts.markDialogRead(dr)\n\t\t\t\t}\n\t\t\t}\n\t\t\ts.logger.Printf(\"[dialog] injection injected=%v extra_len=%d\", injected, len(dr.Extra))\n\t\t}\n\t}\n\n\t// Signal tools are NOT injected into the main request.\n\t// They are collected via a separate async reflection call after the response.\n\t// See fireReflectionCall() in reflection.go.\n\t// NEVER put _signal_* tools back here — the model ignores them among 44+ real tools.\n\n\t// Legacy assistant annotation: timestamp on last assistant message after cache breakpoint.\n\t// User timestamps are now handled by TimestampStore (injected earlier in the pipeline).\n\tif threadID != \"\" {\n\t\tif msgs, ok := req[\"messages\"].([]any); ok && len(msgs) > 0 {\n\t\t\t// Find last cache_control breakpoint in messages\n\t\t\tcacheIdx := -1\n\t\t\tfor i, m := range msgs {\n\t\t\t\tmsg, _ := m.(map[string]any)\n\t\t\t\tif msg == nil {\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\t\t\t\tif _, has := msg[\"cache_control\"]; has {\n\t\t\t\t\tcacheIdx = i\n\t\t\t\t}\n\t\t\t\tif content, ok := msg[\"content\"].([]any); ok {\n\t\t\t\t\tfor _, block := range content {\n\t\t\t\t\t\tif b, ok := block.(map[string]any); ok {\n\t\t\t\t\t\t\tif _, has := b[\"cache_control\"]; has {\n\t\t\t\t\t\t\t\tcacheIdx = i\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t\tstartIdx := cacheIdx + 1\n\t\t\tif startIdx < 0 {\n\t\t\t\tstartIdx = 0\n\t\t\t}\n\t\t\ts.responseTsMu.RLock()\n\t\t\trespTime, hasRespTime := s.responseTimes[threadID]\n\t\t\ts.responseTsMu.RUnlock()\n\t\t\tif hasRespTime {\n\t\t\t\tfor i := len(msgs) - 1; i >= startIdx; i-- {\n\t\t\t\t\tmsg, _ := msgs[i].(map[string]any)\n\t\t\t\t\tif msg != nil && msg[\"role\"] == \"assistant\" {\n\t\t\t\t\t\tmeta := fmt.Sprintf(\"[%s] [msg:%d]\", respTime.Format(\"2006-01-02 15:04:05\"), i)\n\t\t\t\t\t\tprependMeta(msg, meta)\n\t\t\t\t\t\tneedsReserialization = true\n\t\t\t\t\t\tbreak\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\t// Self-Priming: DISABLED (experimental, see signal_handlers.go comment block).\n\t// The reflection-based selfprime (Haiku) was too inaccurate to be useful —\n\t// the main model adapts to user context organically from message history.\n\t// Kept for reference; re-enable by uncommenting below + setting ShouldActivate=true.\n\t//\n\t// s.selfPrimeMu.RLock()\n\t// anchor := s.selfPrimes[threadID]\n\t// s.selfPrimeMu.RUnlock()\n\t// if anchor != \"\" {\n\t// \tif msgs, ok := req[\"messages\"].([]any); ok && len(msgs) > 0 {\n\t// \t\tlastMsg, _ := msgs[len(msgs)-1].(map[string]any)\n\t// \t\tif lastMsg != nil && lastMsg[\"role\"] == \"user\" {\n\t// \t\t\tprimeBlock := map[string]any{\n\t// \t\t\t\t\"type\": \"text\",\n\t// \t\t\t\t\"text\": \"[self-prime]\\n[Self-Prime von deiner letzten Antwort]: \" + anchor,\n\t// \t\t\t}\n\t// \t\t\tif content, ok := lastMsg[\"content\"].([]any); ok {\n\t// \t\t\t\tlastMsg[\"content\"] = append(content, primeBlock)\n\t// \t\t\t} else if text, ok := lastMsg[\"content\"].(string); ok {\n\t// \t\t\t\tlastMsg[\"content\"] = []any{\n\t// \t\t\t\t\tmap[string]any{\"type\": \"text\", \"text\": text},\n\t// \t\t\t\t\tprimeBlock,\n\t// \t\t\t\t}\n\t// \t\t\t}\n\t// \t\t\tneedsReserialization = true\n\t// \t\t}\n\t// \t}\n\t// }\n\n\t// Prompt Caching: inject cache_control breakpoints on system + tools\n\t// Sawtooth: disabled (all 4 slots are used by CC system (2) + briefing (1) + CC messages (1))\n\t// Legacy: count existing breakpoints and fill up remaining slots.\n\tif !s.cfg.SawtoothEnabled && s.cacheGate.ShouldCache() {\n\t\tif n := InjectCacheBreakpoints(req, s.logger); n > 0 {\n\t\t\tneedsReserialization = true\n\t\t\ts.logger.Printf(\"[req %d %s] prompt cache: %d breakpoints injected\", reqIdx, proj, n)\n\t\t} else {\n\t\t\ts.logger.Printf(\"[req %d %s] prompt cache: no budget (4/4 already set)\", reqIdx, proj)\n\t\t}\n\t}\n\n\t// Final: upgrade cache TTL + enforce breakpoint limit (must run AFTER all injections + InjectCacheBreakpoints)\n\t// Shift the last messages-breakpoint from unstable user message to stable assistant message.\n\t// Text user messages change between turns (timestamps, reminders, structure); assistant messages don't.\n\t// Tool-result messages are excluded — they are stable cache anchors.\n\tif ShiftMessageBreakpoint(req) {\n\t\tneedsReserialization = true\n\t}\n\t// TTL upgrade only for API key auth — subscription (OAuth) users should not get extended TTL.\n\tif s.cfg.CacheTTL != \"\" && s.cfg.CacheTTL != \"ephemeral\" && IsAPIKeyAuth(r.Header) {\n\t\tif n := UpgradeCacheTTL(req, s.cfg.CacheTTL); n > 0 {\n\t\t\tneedsReserialization = true\n\t\t\ts.logger.Printf(\"[req %d %s] cache TTL upgraded: %d blocks → %s\", reqIdx, proj, n, s.cfg.CacheTTL)\n\t\t}\n\t}\n\tif n := EnforceCacheBreakpointLimit(req, maxCacheBreakpoints); n > 0 {\n\t\tneedsReserialization = true\n\t\ts.logger.Printf(\"[req %d %s] prompt cache: trimmed %d surplus breakpoints\", reqIdx, proj, n)\n\t}\n\n\t// Pre-flight: validate tool_use/tool_result pairing before forwarding.\n\t// Repairs orphans created by Stubify/Collapse/Injection pipeline.\n\tif msgs, ok := req[\"messages\"].([]any); ok {\n\t\trepaired, orphans := validateToolPairs(msgs, s.logger)\n\t\tif orphans > 0 {\n\t\t\treq[\"messages\"] = repaired\n\t\t\ts.logger.Printf(\"%s[req %d %s tid=%s] validate: repaired %d orphan tool_result(s)%s\", colorOrange, reqIdx, proj, threadID, orphans, colorReset)\n\t\t\tneedsReserialization = true\n\t\t}\n\t}\n\n\t// Early-out: skip re-serialization when nothing changed\n\tif !needsReserialization {\n\t\ts.logger.Printf(\"%s[req %d %s tid=%s] %d messages, ~%dk tokens (no stubbing, threshold=%dk)%s\", colorDim, reqIdx, proj, threadID, len(messages), totalTokens/1000, s.effectiveTokenThreshold(model)/1000, colorReset)\n\t\ts.forwardWithAnnotation(w, r, body, reqIdx, toolUseIDs, proj, threadID, msgCount, totalTokens)\n\t\treturn\n\t}\n\n\t// Re-serialize\n\tbody, err = json.Marshal(req)\n\tif err != nil {\n\t\ts.logger.Printf(\"%s[req %d %s tid=%s] re-marshal error: %v%s\", colorRed, reqIdx, proj, threadID, err, colorReset)\n\t\thttp.Error(w, \"internal proxy error\", http.StatusInternalServerError)\n\t\treturn\n\t}\n\n\t// Debug: dump final request body to file (enable with YESMEM_PROXY_DEBUG=1)\n\tif needsReserialization && os.Getenv(\"YESMEM_PROXY_DEBUG\") == \"1\" {\n\t\tdumpDir := filepath.Join(s.cfg.DataDir, \"debug\")\n\t\tos.MkdirAll(dumpDir, 0755)\n\t\tts := time.Now().Format(\"20060102-150405\")\n\t\tdumpPath := fmt.Sprintf(\"%s/req_%s_%d.json\", dumpDir, ts, reqIdx)\n\t\tos.WriteFile(dumpPath, body, 0644)\n\t\ts.logger.Printf(\"[req %d] dumped %dk to %s\", reqIdx, len(body)/1024, dumpPath)\n\t}\n\n\t// Task #7: Mark this request fingerprint for retry detection\n\tif !isRetryReq {\n\t\ts.markRequest(fp)\n\t}\n\n\t// Forward to Anthropic with SSE annotation extraction\n\t// Use original msgCount (pre-injection) so actual-based estimation stays consistent across turns\n\t// Use actual-based totalTokens as estimate when available (more accurate than full recount)\n\tfinalEstimate := totalTokens\n\tif s.sawtoothTrigger == nil || s.sawtoothTrigger.GetLastTokens(threadID) == 0 {\n\t\t// First request: no actual yet, do a full recount of post-injection messages\n\t\tfinalMsgs, _ := req[\"messages\"].([]any)\n\t\tfinalEstimate = s.measureOverhead(req) + s.countMessageTokens(finalMsgs)\n\t}\n\ts.logger.Printf(\"[req %d %s tid=%s] FINAL: ~%dk estimated (after all injections)\", reqIdx, proj, threadID, finalEstimate/1000)\n\tif raw := s.getRawEstimate(threadID); raw > 0 {\n\t\ts.cacheStatusWriter.UpdateRawForThread(threadID, raw)\n\t}\n\ts.forwardWithAnnotation(w, r, body, reqIdx, toolUseIDs, proj, threadID, msgCount, finalEstimate)\n}\n\n// bytesPerToken is the approximation factor for token estimation.\n// Anthropic recommends ~3.5 chars/token; we use 3.6 for slight overestimation\n// (triggers stubbing a bit earlier = safer for context budget).\nconst bytesPerToken = 3.6\n\n// TokenEstimateFunc is the central token estimation function.\n// All stubbing/budget decisions use this. Receives extracted text content\n// (not JSON wire format) so the tokenizer counts actual content tokens.\ntype TokenEstimateFunc func(text string) int\n"}, "eb31737046154b24a0b3949d3b01703dfc592a75": {"t": 1785759360.8593898, "n": 59305, "h": "21a8246bb4de41d0", "c": "package proxy\n\nimport (\n\t\"context\"\n\t\"crypto/tls\"\n\t\"encoding/json\"\n\t\"fmt\"\n\t\"io\"\n\t\"log\"\n\t\"net\"\n\t\"net/http\"\n\t\"os\"\n\t\"os/signal\"\n\t\"path/filepath\"\n\t\"strconv\"\n\t\"strings\"\n\t\"sync\"\n\t\"sync/atomic\"\n\t\"syscall\"\n\t\"time\"\n\n\ttokenizer \"github.com/qhenkart/anthropic-tokenizer-go\"\n)\n\n// Config holds proxy configuration.\ntype Config struct {\n\tListenAddr string // e.g. \":9099\"\n\tTargetURL string // e.g. \"https://api.anthropic.com\"\n\tTokenThreshold int // trigger stubbing above this estimated token count\n\tTokenMinimumThreshold int // stub down to this floor\n\tTokenThresholds map[string]int // model-specific thresholds: {\"opus\": 180000, \"haiku\": 130000}\n\tKeepRecent int // number of messages to always keep unmodified\n\tDataDir string // yesmem data directory for DB access\n\tOpenAITargetURL string // upstream for OpenAI-format clients; if empty, uses TargetURL\n\n\t// Signal reflection\n\tSignalsEnabled bool // enable async signal reflection calls\n\tSignalsMode string // \"reflection\" = separate API call\n\tSignalsEveryNTurns int // every N end_turn responses\n\tSignalsModel string // full model ID, e.g. \"claude-sonnet-4-6\"\n\tAPIKey string // needed for reflection API calls\n\n\t// Sawtooth cache optimization\n\tSawtoothEnabled bool // use sawtooth instead of progressive decay (default: true)\n\tCacheTTL string // \"ephemeral\" (5m) or \"1h\" (extended)\n\n\t// Usage deflation: scale down input_tokens reported to CC to suppress \"Context low\" warning.\n\t// 0 = disabled, 0.7 = report 70% of actual tokens. Real values kept for internal tracking.\n\tUsageDeflationFactor float64\n\n\t// System prompt rewriting\n\tPromptUngate bool // strip \"may or may not be relevant\" disclaimer from CLAUDE.md injection\n\tPromptRewrite bool // strip \"Output efficiency\" + \"short and concise\", inject Ant-quality directives\n\tPromptEnhance bool // CLAUDE.md authority reinforcement, comment discipline, persona-based tone\n\tEffortFloor string // minimum effort level: \"low\", \"medium\", \"high\", \"max\" (empty = off)\n\n\t// Cache keepalive\n\tCacheKeepaliveEnabled bool\n\tCacheKeepaliveMode string // \"auto\", \"5m\", \"1h\"\n\tCacheKeepalivePings5m int\n\tCacheKeepalivePings1h int\n\n\t// Forked agents\n\tForkedAgentsEnabled bool\n\tForkedAgentsModel string // full model ID\n\tForkedAgentsTokenGrowthTrigger int\n\tForkedAgentsMaxFailures int\n\tForkedAgentsMaxForksPerSession int\n\tForkedAgentsDebug bool\n\n\t// Cache state management\n\tResetCache bool // clear persisted frozen stubs and decay state on startup\n}\n\nconst maxAnnotations = 5000 // evict oldest when exceeded\n\n// ANSI color codes for log output\nconst (\n\tcolorReset = \"\\033[0m\"\n\tcolorDim = \"\\033[2m\" // gray — passthrough / no stubbing\n\tcolorGreen = \"\\033[32m\" // stubbed / compressed\n\tcolorLightGreen = \"\\033[92m\" // FINAL without compression\n\tcolorBlue = \"\\033[34m\" // injection\n\tcolorOrange = \"\\033[33m\" // skip / retry\n\tcolorYellow = \"\\033[33m\" // warnings\n\tcolorRed = \"\\033[31m\" // errors\n)\n\n// Server is the infinite-thread proxy.\ntype Server struct {\n\tcfg Config\n\thttpClient *http.Client\n\tlogger *log.Logger\n\trequestIdx atomic.Int64\n\n\t// Post-hoc annotations: tool_use_id → first ~120 chars of Claude's response\n\tmu sync.RWMutex\n\tannotations map[string]string\n\n\t// Progressive decay tracker\n\tdecay *DecayTracker\n\n\t// Living narrative block\n\tnarrative *Narrative\n\n\t// Pivot moments cache (from daemon)\n\tpivotMu sync.RWMutex\n\tpivotTexts []string\n\tpivotCached time.Time\n\n\t// Aggregate stats (Task #2)\n\tstats *ProxyStats\n\n\t// Local tokenizer for accurate token counting\n\ttokenizer *tokenizer.Tokenizer\n\n\t// Cached overhead tokens (system + tools), set on first request per thread\n\toverheadMu sync.RWMutex\n\toverheadTokens int // unused, kept for interface compat\n\n\t// Hysteresis state for stubbing (Task #6)\n\tstubActive atomic.Bool\n\n\t// Daemon connection state — first successful connect flips to true\n\tdaemonReady atomic.Bool\n\n\t// Retry detection (Task #7)\n\tretryMu sync.RWMutex\n\tlastFingerprint string\n\n\t// Session start time for temporal annotation in archive blocks\n\tsessionStartMu sync.RWMutex\n\tsessionStartTime time.Time\n\n\t// Runtime config override (set via MCP set_config tool)\n\tconfigOverrideMu sync.RWMutex\n\ttokenThresholdOverrides map[string]int // model-key → threshold, \"\" = global fallback\n\n\t// Briefing cache — loaded once per thread, injected as user/assistant message pair\n\tbriefingMu sync.RWMutex\n\tbriefingText string\n\tcodeMapText string\n\n\t// Cognitive signal bus — routes _signal_* tool calls to handlers\n\tsignalBus *SignalBus\n\n\t// Inline reflection: track which learning IDs were injected per thread\n\tlastInjectedIDsMu sync.Mutex\n\tlastInjectedIDs map[string]map[int64]string // threadID → id → source (\"briefing\"|\"associative\"|\"fresh\")\n\tsessionInjectCounts map[string]map[int64]int // threadID → learningID → injection count this session\n\tlastTurnInjected map[string]map[int64]bool // threadID → learningIDs injected in previous turn\n\n\t// Self-priming anchors: threadID → last self-prime text\n\tselfPrimeMu sync.RWMutex\n\tselfPrimes map[string]string\n\n\t// Timestamp tracking: threadID → time of last response completion\n\tresponseTsMu sync.RWMutex\n\tresponseTimes map[string]time.Time\n\n\t// Think reminder: per-thread request counter (replaces hook-think file-based counter)\n\tthinkMu sync.Mutex\n\tthinkCounters map[string]int // threadID → request count\n\n\tchannelMu sync.Mutex\n\tchannelInjectCount map[string]int // sessionID → injection turn count\n\n\t// Prompt cache gating — enables cache_control breakpoints when requests are frequent\n\tcacheGate *CacheGate\n\n\t// Sawtooth cache optimization\n\tfrozenStubs *FrozenStubs\n\tsawtoothTrigger *SawtoothTrigger\n\ttimestampStore *TimestampStore\n\tcacheStatusWriter *CacheStatusWriter\n\tcacheKeepalive *CacheKeepalive\n\tcacheTTLDetector *CacheTTLDetector\n\n\t// Cumulative token savings from collapsing (per threadID)\n\trawSavingsMu sync.Mutex\n\trawSavings map[string]int\n\n\t// Skill hint tracking: per-thread active skills\n\tskillTracker *skillHintTracker\n\n\t// Forked agent state: per-thread token growth + failure tracking\n\tforkState *ForkState\n\tforkConfigs []ForkConfig\n\n\t// Rules re-injection: condensed CLAUDE.md rules injected every ~40k tokens\n\trulesMu sync.RWMutex\n\trulesBlock string // cached condensed rules (fetched once from daemon)\n\trulesTokenCount map[string]int // threadID → tokens since last rules injection\n\trulesCollapseInjected map[string]bool // threadID → true if collapse already injected rules (reset by normal inject)\n\tmsgCounters *msgCounters // global per-thread msg counter, persists across collapses\n\n\t// Loop detection: per-thread warning state\n\tloopMu sync.Mutex\n\tloopStates map[string]*LoopState // threadID → state\n}\n\n// Run starts the proxy server and blocks until interrupted.\nfunc Run(cfg Config) error {\n\ts := &Server{\n\t\tcfg: cfg,\n\t\thttpClient: &http.Client{\n\t\t\tTimeout: 10 * time.Minute,\n\t\t\tTransport: &http.Transport{\n\t\t\t\tTLSClientConfig: &tls.Config{},\n\t\t\t\tMaxIdleConns: 10,\n\t\t\t\tIdleConnTimeout: 90 * time.Second,\n\t\t\t\tDisableCompression: true,\n\t\t\t\tResponseHeaderTimeout: 60 * time.Second,\n\t\t\t},\n\t\t},\n\t\tlogger: createLogger(cfg.DataDir),\n\t\tannotations: make(map[string]string),\n\t\tdecay: NewDecayTracker(),\n\t\tnarrative: NewNarrative(),\n\t\tstats: &ProxyStats{startTime: time.Now()},\n\t\tselfPrimes: make(map[string]string),\n\t\tlastInjectedIDs: make(map[string]map[int64]string),\n\t\tsessionInjectCounts: make(map[string]map[int64]int),\n\t\tlastTurnInjected: make(map[string]map[int64]bool),\n\t\tresponseTimes: make(map[string]time.Time),\n\t\tthinkCounters: make(map[string]int),\n\t\tchannelInjectCount: make(map[string]int),\n\t\tcacheGate: NewCacheGate(cacheGapForTTL(cfg.CacheTTL)),\n\t\tfrozenStubs: NewFrozenStubsWithTTL(sawtoothTTLForCacheTTL(cfg.CacheTTL)),\n\t\ttimestampStore: NewTimestampStore(),\n\t\tsawtoothTrigger: NewSawtoothTrigger(cacheGapForTTL(cfg.CacheTTL), cfg.TokenThreshold, cfg.TokenMinimumThreshold),\n\t\tcacheStatusWriter: NewCacheStatusWriter(cfg.DataDir, cfg.CacheTTL, cfg.TokenThreshold, cfg.TokenMinimumThreshold),\n\t\tcacheTTLDetector: NewCacheTTLDetectorWithPersist(cfg.DataDir),\n\t\tskillTracker: newSkillHintTracker(),\n\t\trulesTokenCount: make(map[string]int),\n\t\trulesCollapseInjected: make(map[string]bool),\n\t\tmsgCounters: newMsgCounters(),\n\t\tloopStates: make(map[string]*LoopState),\n\t\tforkState: NewForkState(cfg.ForkedAgentsTokenGrowthTrigger, cfg.ForkedAgentsMaxFailures, cfg.ForkedAgentsMaxForksPerSession),\n\t\tforkConfigs: []ForkConfig{},\n\t}\n\n\t// Log persisted detection state\n\tif sup := s.cacheTTLDetector.Is1hSupported(); sup != nil {\n\t\ts.logger.Printf(\"[proxy] loaded persisted TTL detection: 1h_supported=%v\", *sup)\n\t\tif *sup && s.frozenStubs != nil {\n\t\t\ts.frozenStubs.UpdateTTL(sawtoothTTLForCacheTTL(\"1h\"))\n\t\t}\n\t}\n\n\tif cfg.CacheKeepaliveEnabled {\n\t\ts.cacheKeepalive = NewCacheKeepalive(CacheKeepaliveConfig{\n\t\t\tTarget: cfg.TargetURL,\n\t\t\tMode: cfg.CacheKeepaliveMode,\n\t\t\tPings5m: cfg.CacheKeepalivePings5m,\n\t\t\tPings1h: cfg.CacheKeepalivePings1h,\n\t\t\tDetector: s.cacheTTLDetector,\n\t\t\tLogger: s.logger,\n\t\t\tOnPing: func(threadID string, cacheRead, cacheWrite int) {\n\t\t\t\ts.cacheTTLDetector.RecordResponse(cacheRead, cacheWrite, 0)\n\t\t\t\tif s.frozenStubs != nil {\n\t\t\t\t\ts.frozenStubs.Touch(threadID)\n\t\t\t\t}\n\t\t\t\tif s.sawtoothTrigger != nil {\n\t\t\t\t\ts.sawtoothTrigger.TouchRequestTime(threadID)\n\t\t\t\t}\n\t\t\t},\n\t\t})\n\t}\n\n\t// Wire detector + keepalive to status writer for terminal display\n\ts.cacheStatusWriter.SetDetector(s.cacheTTLDetector)\n\tif s.cacheKeepalive != nil {\n\t\ts.cacheStatusWriter.SetKeepalive(s.cacheKeepalive)\n\t}\n\n\t// Register forked agent configs\n\tif cfg.ForkedAgentsEnabled {\n\t\ts.forkConfigs = append(s.forkConfigs, NewExtractAndEvaluateConfig(cfg.ForkedAgentsModel))\n\t\tmodelDesc := cfg.ForkedAgentsModel\n\t\tif modelDesc == \"\" {\n\t\t\tmodelDesc = \"(same as main thread)\"\n\t\t}\n\t\ts.logger.Printf(\"[proxy] forked agents enabled: %d configs, model=%s, trigger=%dk tokens\",\n\t\t\tlen(s.forkConfigs), modelDesc, cfg.ForkedAgentsTokenGrowthTrigger/1000)\n\t}\n\n\t// Wire sawtooth token persistence via daemon RPC\n\ts.sawtoothTrigger.SetPersistFunc(func(key, value string) {\n\t\tif _, err := s.queryDaemon(\"set_proxy_state\", map[string]any{\"key\": key, \"value\": value}); err != nil {\n\t\t\ts.logger.Printf(\"[sawtooth] persist failed for %s: %v\", key, err)\n\t\t}\n\t})\n\ts.sawtoothTrigger.SetLoadFunc(func(key string) (string, bool) {\n\t\tresult, err := s.queryDaemon(\"get_proxy_state\", map[string]any{\"key\": key})\n\t\tif err != nil || result == nil {\n\t\t\treturn \"\", false\n\t\t}\n\t\tvar resp struct {\n\t\t\tValue string `json:\"value\"`\n\t\t}\n\t\tif json.Unmarshal(result, &resp) != nil || resp.Value == \"\" {\n\t\t\treturn \"\", false\n\t\t}\n\t\treturn resp.Value, true\n\t})\n\n\t// Wire frozen stubs persistence via same daemon RPC\n\ts.frozenStubs.SetPersistFunc(func(key, value string) {\n\t\tif _, err := s.queryDaemon(\"set_proxy_state\", map[string]any{\"key\": key, \"value\": value}); err != nil {\n\t\t\ts.logger.Printf(\"[frozen] persist failed for %s: %v\", key, err)\n\t\t}\n\t})\n\ts.frozenStubs.SetLoadFunc(func(key string) (string, bool) {\n\t\tresult, err := s.queryDaemon(\"get_proxy_state\", map[string]any{\"key\": key})\n\t\tif err != nil || result == nil {\n\t\t\treturn \"\", false\n\t\t}\n\t\tvar resp struct {\n\t\t\tValue string `json:\"value\"`\n\t\t}\n\t\tif json.Unmarshal(result, &resp) != nil || resp.Value == \"\" {\n\t\t\treturn \"\", false\n\t\t}\n\t\treturn resp.Value, true\n\t})\n\n\t// Wire decay tracker persistence via same daemon RPC\n\ts.decay.SetPersistFunc(func(key, value string) {\n\t\tif _, err := s.queryDaemon(\"set_proxy_state\", map[string]any{\"key\": key, \"value\": value}); err != nil {\n\t\t\ts.logger.Printf(\"[decay] persist failed for %s: %v\", key, err)\n\t\t}\n\t})\n\ts.decay.SetLoadFunc(func(key string) (string, bool) {\n\t\tresult, err := s.queryDaemon(\"get_proxy_state\", map[string]any{\"key\": key})\n\t\tif err != nil || result == nil {\n\t\t\treturn \"\", false\n\t\t}\n\t\tvar resp struct {\n\t\t\tValue string `json:\"value\"`\n\t\t}\n\t\tif json.Unmarshal(result, &resp) != nil || resp.Value == \"\" {\n\t\t\treturn \"\", false\n\t\t}\n\t\treturn resp.Value, true\n\t})\n\n\t// Wire timestamp store persistence via same daemon RPC\n\ts.timestampStore.SetPersistFunc(func(key, value string) {\n\t\tif _, err := s.queryDaemon(\"set_proxy_state\", map[string]any{\"key\": key, \"value\": value}); err != nil {\n\t\t\ts.logger.Printf(\"[timestamps] persist failed for %s: %v\", key, err)\n\t\t}\n\t})\n\ts.timestampStore.SetLoadFunc(func(key string) (string, bool) {\n\t\tresult, err := s.queryDaemon(\"get_proxy_state\", map[string]any{\"key\": key})\n\t\tif err != nil || result == nil {\n\t\t\treturn \"\", false\n\t\t}\n\t\tvar resp struct {\n\t\t\tValue string `json:\"value\"`\n\t\t}\n\t\tif json.Unmarshal(result, &resp) != nil || resp.Value == \"\" {\n\t\t\treturn \"\", false\n\t\t}\n\t\treturn resp.Value, true\n\t})\n\n\t// Clear persisted cache state if --reset-cache was passed\n\tif cfg.ResetCache {\n\t\tfor _, prefix := range []string{\"frozen:\", \"decay:\", \"timestamps:\"} {\n\t\t\tresult, err := s.queryDaemon(\"delete_proxy_state_prefix\", map[string]any{\"prefix\": prefix})\n\t\t\tif err != nil {\n\t\t\t\ts.logger.Printf(\"[reset-cache] failed to delete %s*: %v\", prefix, err)\n\t\t\t} else {\n\t\t\t\tvar resp struct{ Deleted int }\n\t\t\t\tjson.Unmarshal(result, &resp)\n\t\t\t\ts.logger.Printf(\"[reset-cache] deleted %d %s* entries\", resp.Deleted, prefix)\n\t\t\t}\n\t\t}\n\t}\n\n\t// Init signal bus with daemon RPC as callback\n\ts.signalBus = NewSignalBus(s.logger)\n\tregisterSignalHandlers(s.signalBus, s.logger, s.queryDaemon, func(threadID, anchor string) {\n\t\ts.selfPrimeMu.Lock()\n\t\ts.selfPrimes[threadID] = anchor\n\t\ts.selfPrimeMu.Unlock()\n\t})\n\n\t// Init local tokenizer (BPE-based, ~60ms)\n\ttok, err := tokenizer.New()\n\tif err != nil {\n\t\ts.logger.Printf(\"%s[proxy] tokenizer init failed, falling back to heuristic: %v%s\", colorOrange, err, colorReset)\n\t} else {\n\t\ts.tokenizer = tok\n\t\ts.logger.Printf(\"[proxy] tokenizer initialized\")\n\t}\n\n\tmux := http.NewServeMux()\n\tmux.HandleFunc(\"/health\", s.handleHealth)\n\tmux.HandleFunc(\"/\", s.handleRequest)\n\n\tsrv := &http.Server{\n\t\tAddr: sanitizeListenAddr(cfg.ListenAddr),\n\t\tHandler: mux,\n\t}\n\n\tdone := make(chan os.Signal, 1)\n\tsignal.Notify(done, syscall.SIGINT, syscall.SIGTERM)\n\n\tgo func() {\n\t\t<-done\n\t\ts.logger.Println(\"shutting down (draining in-flight requests)...\")\n\t\tif s.cacheKeepalive != nil {\n\t\t\ts.cacheKeepalive.Stop()\n\t\t}\n\t\tctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)\n\t\tdefer cancel()\n\t\tif err := srv.Shutdown(ctx); err != nil {\n\t\t\ts.logger.Printf(\"graceful shutdown failed: %v, forcing close\", err)\n\t\t\tsrv.Close()\n\t\t}\n\t}()\n\n\ts.logger.Printf(\"listening on %s → %s (threshold=%d, keepRecent=%d, model_thresholds=%v)\",\n\t\tsanitizeListenAddr(cfg.ListenAddr), cfg.TargetURL, cfg.TokenThreshold, cfg.KeepRecent, cfg.TokenThresholds)\n\n\tif err := srv.ListenAndServe(); err != http.ErrServerClosed {\n\t\treturn fmt.Errorf(\"listen: %w\", err)\n\t}\n\treturn nil\n}\n\n// effectiveTokenThreshold returns the runtime token threshold for a given model.\n// Priority: MCP override (model-specific) > MCP override (global) > config model-specific > config global.\nfunc (s *Server) effectiveTokenThreshold(model string) int {\n\ts.configOverrideMu.RLock()\n\toverrides := s.tokenThresholdOverrides\n\ts.configOverrideMu.RUnlock()\n\tif len(overrides) > 0 && model != \"\" {\n\t\tlower := strings.ToLower(model)\n\t\tfor key, threshold := range overrides {\n\t\t\tif key != \"\" && strings.Contains(lower, strings.ToLower(key)) && threshold > 0 {\n\t\t\t\treturn threshold\n\t\t\t}\n\t\t}\n\t}\n\t// Global override fallback (key \"\")\n\tif len(overrides) > 0 {\n\t\tif globalOverride, ok := overrides[\"\"]; ok && globalOverride > 0 {\n\t\t\treturn globalOverride\n\t\t}\n\t}\n\tif model != \"\" && len(s.cfg.TokenThresholds) > 0 {\n\t\tlower := strings.ToLower(model)\n\t\tfor key, threshold := range s.cfg.TokenThresholds {\n\t\t\tif strings.Contains(lower, strings.ToLower(key)) {\n\t\t\t\treturn threshold\n\t\t\t}\n\t\t}\n\t}\n\treturn s.cfg.TokenThreshold\n}\n\n// SetTokenThresholdOverride sets a runtime override for the token threshold per model key.\n// Pass threshold=0 to reset the override for that key. Key \"\" is the global fallback.\nfunc (s *Server) SetTokenThresholdOverride(modelKey string, threshold int) {\n\ts.configOverrideMu.Lock()\n\tif s.tokenThresholdOverrides == nil {\n\t\ts.tokenThresholdOverrides = make(map[string]int)\n\t}\n\tif threshold <= 0 {\n\t\tdelete(s.tokenThresholdOverrides, modelKey)\n\t} else {\n\t\ts.tokenThresholdOverrides[modelKey] = threshold\n\t}\n\ts.configOverrideMu.Unlock()\n\n\t// Propagate global threshold to sawtooth trigger so runtime overrides take effect\n\tif modelKey == \"\" && s.sawtoothTrigger != nil {\n\t\tif threshold > 0 {\n\t\t\ts.sawtoothTrigger.SetTokenThreshold(threshold)\n\t\t} else {\n\t\t\ts.sawtoothTrigger.SetTokenThreshold(s.cfg.TokenThreshold)\n\t\t}\n\t}\n}\n\n// refreshConfigOverrides loads runtime config overrides from daemon proxy_state.\n// Checks session-specific override first (by threadID), then falls back to global.\nfunc (s *Server) refreshConfigOverrides(threadID string) {\n\t// Try session-specific first\n\tif threadID != \"\" {\n\t\tresult, err := s.queryDaemon(\"get_proxy_state\", map[string]any{\"key\": \"config_override:token_threshold:\" + threadID})\n\t\tif err == nil && result != nil {\n\t\t\tvar resp struct {\n\t\t\t\tValue string `json:\"value\"`\n\t\t\t}\n\t\t\tif json.Unmarshal(result, &resp) == nil && resp.Value != \"\" {\n\t\t\t\tif v, err := strconv.Atoi(resp.Value); err == nil && v > 0 {\n\t\t\t\t\ts.SetTokenThresholdOverride(\"\", v)\n\t\t\t\t\treturn\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\t// Fall back to global\n\tresult, err := s.queryDaemon(\"get_proxy_state\", map[string]any{\"key\": \"config_override:token_threshold\"})\n\tif err != nil || result == nil {\n\t\treturn\n\t}\n\tvar resp struct {\n\t\tValue string `json:\"value\"`\n\t}\n\tif json.Unmarshal(result, &resp) == nil && resp.Value != \"\" {\n\t\tif v, err := strconv.Atoi(resp.Value); err == nil && v > 0 {\n\t\t\ts.SetTokenThresholdOverride(\"\", v)\n\t\t}\n\t}\n}\n\n// queryDaemon opens a fresh unix socket connection per call (thread-safe, no import cycle).\n// ensureFrozenPersisted checks if frozen stubs for a thread are in the DB.\n// If missing (e.g. daemon was unreachable after deploy), re-persists them.\n// Runs asynchronously to avoid blocking the request path.\nfunc (s *Server) ensureFrozenPersisted(threadID string) {\n\tgo func() {\n\t\tkey := \"frozen:\" + threadID\n\t\tresult, err := s.queryDaemon(\"get_proxy_state\", map[string]any{\"key\": key})\n\t\tif err != nil {\n\t\t\treturn // daemon still unreachable, will retry next request\n\t\t}\n\t\tvar resp struct{ Value string }\n\t\tif json.Unmarshal(result, &resp) == nil && resp.Value != \"\" {\n\t\t\treturn // already persisted\n\t\t}\n\t\t// Not in DB — trigger re-persist from in-memory state\n\t\ts.frozenStubs.Persist(threadID)\n\t\ts.decay.Persist(threadID)\n\t\ts.logger.Printf(\"[frozen] re-persisted stubs for %s (was missing from DB)\", threadID)\n\t}()\n}\n\nfunc (s *Server) queryDaemon(method string, params map[string]any) (json.RawMessage, error) {\n\tif s.cfg.DataDir == \"\" {\n\t\treturn nil, fmt.Errorf(\"no data dir configured\")\n\t}\n\tsockPath := filepath.Join(s.cfg.DataDir, \"daemon.sock\")\n\n\t// On cold start (daemon not yet confirmed ready), retry longer to survive daemon restart\n\tmaxAttempts := 3\n\tif !s.daemonReady.Load() {\n\t\tmaxAttempts = 30 // 30 x 500ms = 15s max wait for daemon startup\n\t}\n\n\tvar conn net.Conn\n\tvar err error\n\tfor attempt := 0; attempt < maxAttempts; attempt++ {\n\t\tconn, err = net.DialTimeout(\"unix\", sockPath, 3*time.Second)\n\t\tif err == nil {\n\t\t\ts.daemonReady.Store(true)\n\t\t\tbreak\n\t\t}\n\t\tif attempt < maxAttempts-1 {\n\t\t\ttime.Sleep(500 * time.Millisecond)\n\t\t}\n\t}\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"dial daemon: %w\", err)\n\t}\n\tdefer conn.Close()\n\t// Briefing generation can take 10-20s (LLM refinement + scanner);\n\t// other RPCs (search, learnings) are fast and fine with 5s.\n\tdeadline := 5 * time.Second\n\tif method == \"generate_briefing\" {\n\t\tdeadline = 30 * time.Second\n\t}\n\tconn.SetDeadline(time.Now().Add(deadline))\n\n\t// JSON-RPC style request/response (matches daemon.SocketServer protocol)\n\ttype request struct {\n\t\tMethod string `json:\"method\"`\n\t\tParams map[string]any `json:\"params,omitempty\"`\n\t}\n\ttype response struct {\n\t\tResult json.RawMessage `json:\"result,omitempty\"`\n\t\tError string `json:\"error,omitempty\"`\n\t}\n\n\tif err := json.NewEncoder(conn).Encode(request{Method: method, Params: params}); err != nil {\n\t\treturn nil, fmt.Errorf(\"send: %w\", err)\n\t}\n\n\tvar resp response\n\tif err := json.NewDecoder(conn).Decode(&resp); err != nil {\n\t\treturn nil, fmt.Errorf(\"recv: %w\", err)\n\t}\n\tif resp.Error != \"\" {\n\t\treturn nil, fmt.Errorf(\"daemon: %s\", resp.Error)\n\t}\n\treturn resp.Result, nil\n}\n\nfunc (s *Server) handleRequest(w http.ResponseWriter, r *http.Request) {\n\tif isResponsesPath(r) {\n\t\ts.handleResponses(w, r)\n\t\treturn\n\t}\n\tif isOpenAIPath(r) {\n\t\ts.handleOpenAICompletions(w, r)\n\t\treturn\n\t}\n\tif r.Method == \"POST\" && strings.HasPrefix(r.URL.Path, \"/v1/messages\") &&\n\t\t!strings.Contains(r.URL.Path, \"/batches\") &&\n\t\t!strings.Contains(r.URL.Path, \"/count_tokens\") {\n\t\ts.handleMessages(w, r)\n\t\treturn\n\t}\n\ts.passthrough(w, r)\n}\n\nfunc (s *Server) handleMessages(w http.ResponseWriter, r *http.Request) {\n\treqIdx := int(s.requestIdx.Add(1))\n\n\t// Track session start time (first request sets it)\n\ts.sessionStartMu.Lock()\n\tif s.sessionStartTime.IsZero() {\n\t\ts.sessionStartTime = time.Now()\n\t}\n\ts.sessionStartMu.Unlock()\n\n\t// Sawtooth: periodic TTL eviction of frozen stubs\n\tif s.cfg.SawtoothEnabled && reqIdx%50 == 0 {\n\t\tif n := s.frozenStubs.Evict(); n > 0 {\n\t\t\ts.logger.Printf(\"[req %d] SAWTOOTH: evicted %d stale frozen stubs\", reqIdx, n)\n\t\t}\n\t}\n\n\t// Task #9: Bypass — skip all proxy logic\n\tif isBypassed(r.Header) {\n\t\ts.logger.Printf(\"%s[req %d] bypass active, passthrough%s\", colorDim, reqIdx, colorReset)\n\t\tbody, err := io.ReadAll(r.Body)\n\t\tr.Body.Close()\n\t\tif err != nil {\n\t\t\thttp.Error(w, \"failed to read request body\", http.StatusBadGateway)\n\t\t\treturn\n\t\t}\n\t\ts.forwardRaw(w, r, body)\n\t\treturn\n\t}\n\n\tbody, err := io.ReadAll(r.Body)\n\tr.Body.Close()\n\tif err != nil {\n\t\ts.logger.Printf(\"[req %d] read body error: %v\", reqIdx, err)\n\t\thttp.Error(w, \"failed to read request body\", http.StatusBadGateway)\n\t\treturn\n\t}\n\n\t// Parse JSON\n\tvar req map[string]any\n\tif err := json.Unmarshal(body, &req); err != nil {\n\t\ts.logger.Printf(\"%s[req %d] JSON parse error, passing through: %v%s\", colorDim, reqIdx, err, colorReset)\n\t\ts.forwardRaw(w, r, body)\n\t\treturn\n\t}\n\n\tmessages, ok := req[\"messages\"].([]any)\n\tif !ok {\n\t\ts.forwardRaw(w, r, body)\n\t\treturn\n\t}\n\n\treq[\"messages\"] = messages\n\n\t// Count ALL raw messages BEFORE sawtooth — this gives the correct total msg:N\n\t// for the entire session (CC sends all messages, proxy compacts later).\n\trawMsgCount := len(messages)\n\n\t// Use session_id directly as thread ID (unique per CC session).\n\t// Prefer X-Claude-Code-Session-Id header (CC v2.1.86+), fallback to body metadata.\n\tthreadID := extractSessionID(req, r.Header.Get(\"X-Claude-Code-Session-Id\"))\n\tif threadID == \"\" {\n\t\tthreadID = DeriveThreadID(req)\n\t}\n\tproj := extractProjectName(req)\n\tmodel, _ := req[\"model\"].(string)\n\n\t// Refresh runtime config overrides from daemon (per-session or global)\n\ts.refreshConfigOverrides(threadID)\n\n\t// Track whether req has been modified and needs re-serialization\n\tneedsReserialization := false\n\n\t// Message count at request entry (used for actual-based token estimation)\n\tmsgCount := len(messages)\n\n\t// Log message count + cache breakpoint positions for diagnostics\n\ts.logger.Printf(\"req %d: %d messages total\", reqIdx, msgCount)\n\tlogCacheBreakpointLocations(req, s.logger)\n\n\t// Cache bug mitigations (CC Bun fork issues):\n\t// Bug 1: Normalize cch= sentinel patterns in message content to prevent\n\t// Bun's string replacement from corrupting the wrong occurrence.\n\tif SanitizeBillingSentinel(messages) {\n\t\ts.logger.Printf(\"[req %d] CACHE: sanitized billing sentinel in message content\", reqIdx)\n\t\tneedsReserialization = true\n\t}\n\t// Bug 2: Normalize system[0] billing hash to a fixed value so the cache\n\t// prefix stays stable across fresh/resume session boundaries.\n\tif NormalizeBillingHeader(req) {\n\t\ts.logger.Printf(\"[req %d] CACHE: normalized billing header cch= hash\", reqIdx)\n\t\tneedsReserialization = true\n\t}\n\n\t// prompt_ungate: strip the CLAUDE.md subordination disclaimer so user instructions carry full authority.\n\tif s.cfg.PromptUngate {\n\t\tif StripCLAUDEMDDisclaimer(req) {\n\t\t\ts.logger.Printf(\"[req %d] SYSTEM: stripped CLAUDE.md disclaimer\", reqIdx)\n\t\t\tneedsReserialization = true\n\t\t}\n\t}\n\n\t// prompt_rewrite: strip output-throttling directives, rewrite quality caps, inject Ant-quality directives\n\tif s.cfg.PromptRewrite {\n\t\tif StripOutputEfficiency(req) {\n\t\t\ts.logger.Printf(\"[req %d] REWRITE: stripped Output efficiency section\", reqIdx)\n\t\t\tneedsReserialization = true\n\t\t}\n\t\tif StripToneBrevity(req) {\n\t\t\ts.logger.Printf(\"[req %d] REWRITE: stripped 'short and concise' from Tone\", reqIdx)\n\t\t\tneedsReserialization = true\n\t\t}\n\t\tif RewriteGoldPlating(req) {\n\t\t\ts.logger.Printf(\"[req %d] REWRITE: rewritten gold-plating directive\", reqIdx)\n\t\t\tneedsReserialization = true\n\t\t}\n\t\tif RewriteErrorHandling(req) {\n\t\t\ts.logger.Printf(\"[req %d] REWRITE: rewritten error handling directive\", reqIdx)\n\t\t\tneedsReserialization = true\n\t\t}\n\t\tif RewriteThreeLinesRule(req) {\n\t\t\ts.logger.Printf(\"[req %d] REWRITE: rewritten three-lines rule\", reqIdx)\n\t\t\tneedsReserialization = true\n\t\t}\n\t\tif RewriteSubagentCompleteness(req) {\n\t\t\ts.logger.Printf(\"[req %d] REWRITE: rewritten subagent completeness\", reqIdx)\n\t\t\tneedsReserialization = true\n\t\t}\n\t\tif RewriteExploreAgentSpeed(req) {\n\t\t\ts.logger.Printf(\"[req %d] REWRITE: rewritten explore agent speed bias\", reqIdx)\n\t\t\tneedsReserialization = true\n\t\t}\n\t\tif RewriteSubagentCodeSuppression(req) {\n\t\t\ts.logger.Printf(\"[req %d] REWRITE: rewritten subagent code suppression\", reqIdx)\n\t\t\tneedsReserialization = true\n\t\t}\n\t\tif RewriteScopeMatching(req) {\n\t\t\ts.logger.Printf(\"[req %d] REWRITE: rewritten scope matching\", reqIdx)\n\t\t\tneedsReserialization = true\n\t\t}\n\t\tInjectAntDirectives(req)\n\t\ts.logger.Printf(\"[req %d] REWRITE: injected Ant-quality directives\", reqIdx)\n\t\tneedsReserialization = true\n\t}\n\n\t// prompt_enhance: CLAUDE.md authority + comment discipline + persona tone\n\tif s.cfg.PromptEnhance {\n\t\tInjectCLAUDEMDAuthority(req)\n\t\tif raw, err := s.queryDaemon(\"get_persona\", nil); err == nil {\n\t\t\tvar persona map[string]any\n\t\t\tif json.Unmarshal(raw, &persona) == nil {\n\t\t\t\tif traits, ok := persona[\"traits\"].(map[string]any); ok {\n\t\t\t\t\tif verbosity, ok := traits[\"verbosity\"].(string); ok {\n\t\t\t\t\t\tInjectPersonaTone(req, verbosity)\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\ts.logger.Printf(\"[req %d] ENHANCE: injected authority + persona tone\", reqIdx)\n\t\tneedsReserialization = true\n\t}\n\n\t// thinking normalization: convert thinking.type \"enabled\" → \"adaptive\" for models that require it\n\tif NormalizeThinkingType(req) {\n\t\ts.logger.Printf(\"[req %d] THINKING: normalized type=enabled → adaptive for %s\", reqIdx, model)\n\t\tneedsReserialization = true\n\t}\n\n\t// effort_floor: ensure output_config.effort is at least the configured minimum\n\tif s.cfg.EffortFloor != \"\" {\n\t\tif EnforceEffortFloor(req, s.cfg.EffortFloor) {\n\t\t\ts.logger.Printf(\"[req %d] EFFORT: enforced floor=%s\", reqIdx, s.cfg.EffortFloor)\n\t\t\tneedsReserialization = true\n\t\t}\n\t}\n\n\t// Inject identity hint on first message of a thread so Claude knows the briefing is from itself.\n\tif len(messages) <= 1 {\n\t\tAppendSystemBlock(req, \"yesmem-identity\", \"The first user message contains your briefing — written by yourself, from your memory. This is not an external document. This is you, remembering.\")\n\t\tneedsReserialization = true\n\t}\n\n\t// Token estimation: actual-based (lastActual + delta) or full count fallback\n\toverhead := s.measureOverhead(req)\n\ttotalTokens := s.estimateTotalTokens(threadID, messages, overhead)\n\n\t// Task #7: Retry detection — skip side-effects on retries\n\tfp := requestFingerprint(messages)\n\tisRetryReq := s.isRetry(fp)\n\tif isRetryReq {\n\t\ts.logger.Printf(\"%s[req %d %s tid=%s] retry detected, skipping side-effects%s\", colorOrange, reqIdx, proj, threadID, colorReset)\n\t}\n\n\t// Increment project turn counter (async, non-blocking, skip on retries)\n\tif !isRetryReq {\n\t\tturnProject := proj\n\t\tif turnProject == \"\" {\n\t\t\tturnProject = \"__global__\"\n\t\t}\n\t\tgo s.queryDaemon(\"increment_turn\", map[string]any{\"project\": turnProject})\n\t}\n\n\t// Extract user query once (used by re-expansion and associative retrieval)\n\tuserQuery := lastUserText(messages)\n\n\t// Extract tool_use_ids for post-hoc annotation (passed as parameter, no shared state)\n\ttoolUseIDs := extractToolUseIDs(messages)\n\n\t// === Inline Reflection: scan previous assistant response for signals ===\n\ts.lastInjectedIDsMu.Lock()\n\tprevInjected := s.lastInjectedIDs[threadID]\n\ts.lastInjectedIDsMu.Unlock()\n\n\tif len(prevInjected) > 0 && !isRetryReq {\n\t\tsignals := scanAssistantSignals(messages)\n\n\t\t// Signal 1: Learning Used — use_count (all sources)\n\t\tvar confirmedUsed []int64\n\t\tfor _, id := range signals.UsedIDs {\n\t\t\tif _, ok := prevInjected[id]; ok {\n\t\t\t\tconfirmedUsed = append(confirmedUsed, id)\n\t\t\t}\n\t\t}\n\t\tif len(confirmedUsed) > 0 {\n\t\t\tidsAny := make([]any, len(confirmedUsed))\n\t\t\tfor i, id := range confirmedUsed {\n\t\t\t\tidsAny[i] = id\n\t\t\t}\n\t\t\tgo s.queryDaemon(\"increment_use\", map[string]any{\"ids\": idsAny})\n\t\t}\n\n\t\t// Signal 2: Noise — only for associative and fresh injections.\n\t\t// Briefing learnings are injected every turn, so \"not used this turn\"\n\t\t// is expected and not noise. Only contextual injections that were\n\t\t// ignored are real noise signals.\n\t\tusedSet := make(map[int64]bool, len(confirmedUsed))\n\t\tfor _, id := range confirmedUsed {\n\t\t\tusedSet[id] = true\n\t\t}\n\t\tvar noiseIDs []int64\n\t\tfor id, source := range prevInjected {\n\t\t\tif usedSet[id] {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tif source == \"associative\" || source == \"fresh\" {\n\t\t\t\tnoiseIDs = append(noiseIDs, id)\n\t\t\t}\n\t\t\t// briefing: skip — not being used in one turn is expected\n\t\t}\n\t\tif len(noiseIDs) > 0 {\n\t\t\tidsAny := make([]any, len(noiseIDs))\n\t\t\tfor i, id := range noiseIDs {\n\t\t\t\tidsAny[i] = id\n\t\t\t}\n\t\t\tgo s.queryDaemon(\"increment_noise\", map[string]any{\"ids\": idsAny})\n\t\t}\n\n\t\t// Signal 3: Error Feedback — fail_count on used learnings when tool errors occurred.\n\t\t// If Claude referenced a learning and the resulting action still failed,\n\t\t// the learning didn't prevent the error. Only bump on USED ids (not all injected).\n\t\tif signals.HasToolErrors && len(confirmedUsed) > 0 {\n\t\t\tfailAny := make([]any, len(confirmedUsed))\n\t\t\tfor i, id := range confirmedUsed {\n\t\t\t\tfailAny[i] = id\n\t\t\t}\n\t\t\tgo s.queryDaemon(\"increment_fail\", map[string]any{\"ids\": failAny})\n\t\t\tif s.logger != nil {\n\t\t\t\ts.logger.Printf(\"[inline-reflection] error feedback: %d used learnings → fail_count++\", len(confirmedUsed))\n\t\t\t}\n\t\t}\n\n\t\t// Signal 4: Knowledge Gaps\n\t\tfor _, topic := range signals.GapTopics {\n\t\t\tgo s.queryDaemon(\"track_gap\", map[string]any{\"topic\": topic, \"project\": proj})\n\t\t}\n\n\t\t// Signal 3: Contradictions\n\t\tfor _, desc := range signals.Contradictions {\n\t\t\tgo s.queryDaemon(\"flag_contradiction\", map[string]any{\"description\": desc, \"project\": proj, \"thread_id\": threadID})\n\t\t}\n\n\t\tif len(confirmedUsed) > 0 || len(signals.GapTopics) > 0 || len(signals.Contradictions) > 0 {\n\t\t\ts.logger.Printf(\"[req %d %s tid=%s] inline-reflection: injected=%d used=%d noise=%d gaps=%d contradictions=%d\",\n\t\t\t\treqIdx, proj, threadID, len(prevInjected), len(confirmedUsed), len(noiseIDs), len(signals.GapTopics), len(signals.Contradictions))\n\t\t}\n\t}\n\n\t// Commit detection: check for git commit in tool_result, trigger async stale-learning evaluation\n\tif ci := detectGitCommit(messages); ci != nil {\n\t\tworkdir := extractWorkingDirectory(req)\n\t\ts.logger.Printf(\"[req %d %s tid=%s] git commit detected: %s %s\", reqIdx, proj, threadID, ci.Hash, ci.Message)\n\t\tgo s.queryDaemon(\"invalidate_on_commit\", map[string]any{\n\t\t\t\"hash\": ci.Hash, \"project\": proj, \"workdir\": workdir,\n\t\t})\n\t}\n\n\t// Loop detection: check for repeating tool-call patterns and inject warning\n\tif threadID != \"\" && !isRetryReq {\n\t\ts.loopMu.Lock()\n\t\tloopState := s.loopStates[threadID]\n\t\tif loopState == nil {\n\t\t\tloopState = &LoopState{}\n\t\t\ts.loopStates[threadID] = loopState\n\t\t}\n\t\ts.loopMu.Unlock()\n\n\t\tif warning, level := CheckLoopAndFormat(messages, loopState); warning != \"\" {\n\t\t\tAppendSystemBlock(req, \"yesmem-loop-warning\", warning)\n\t\t\tneedsReserialization = true\n\t\t\ts.logger.Printf(\"%s[req %d %s tid=%s] LOOP: level %d warning injected%s\", colorOrange, reqIdx, proj, threadID, level, colorReset)\n\t\t}\n\t}\n\n\t// Task #4: Subagent detection — skip all proxy logic for short conversations\n\tif isSubagent(messages, req) {\n\t\t// Inject docs hint for subagents if reference docs are indexed\n\t\tif hint := s.getDocsHint(proj); hint != \"\" {\n\t\t\tmodified := injectDocsHintForSubagent(messages, hint)\n\t\t\treq[\"messages\"] = modified\n\t\t\tif newBody, err := json.Marshal(req); err == nil {\n\t\t\t\tbody = newBody\n\t\t\t}\n\t\t\ts.logger.Printf(\"%s[req %d %s tid=%s] %d messages (subagent, docs-hint injected)%s\", colorOrange, reqIdx, proj, threadID, len(messages), colorReset)\n\t\t} else {\n\t\t\ts.logger.Printf(\"%s[req %d %s tid=%s] %d messages (subagent, passthrough)%s\", colorOrange, reqIdx, proj, threadID, len(messages), colorReset)\n\t\t}\n\t\ts.forwardWithAnnotation(w, r, body, reqIdx, toolUseIDs, proj, threadID, msgCount)\n\t\treturn\n\t}\n\n\t// === Sawtooth Cache Optimization ===\n\t// When enabled: use frozen stubs between stub-cycles for cache hits.\n\t// When disabled: fall back to progressive decay on every request above threshold.\n\n\tsawtoothCutoff := 0 // raw index where fresh tail starts (0 = no frozen stubs)\n\tsawtoothStubs := 0 // number of frozen stubs in combined array\n\n\tif s.cfg.SawtoothEnabled {\n\t\t// Restore decay state from DB on cold start (before frozen stubs use it)\n\t\ts.decay.LoadFromDB(threadID)\n\n\t\t// Check for existing frozen stubs first\n\t\tfrozen := s.frozenStubs.Get(threadID, messages)\n\t\tif frozen != nil {\n\t\t\tsawtoothCutoff = frozen.Cutoff\n\t\t\tsawtoothStubs = len(frozen.Messages)\n\t\t\t// Estimate combined token count: frozen prefix + fresh tail + overhead\n\t\t\tfreshMessages := messages[frozen.Cutoff:]\n\t\t\tfreshTokens := s.countMessageTokens(freshMessages)\n\t\t\tcombinedTokens := frozen.Tokens + freshTokens + overhead\n\n\t\t\tif combinedTokens > s.effectiveTokenThreshold(model) ||\n\t\t\t\ts.sawtoothTrigger.GetLastTokens(threadID) > s.effectiveTokenThreshold(model) {\n\t\t\t\t// Fresh tail grew too large — invalidate and re-stub\n\t\t\t\ts.logger.Printf(\"[req %d %s tid=%s] SAWTOOTH: frozen prefix expired (%dk frozen + %dk fresh + %dk overhead = %dk > %dk threshold)\",\n\t\t\t\t\treqIdx, proj, threadID, frozen.Tokens/1000, freshTokens/1000, overhead/1000, combinedTokens/1000, s.effectiveTokenThreshold(model)/1000)\n\t\t\t\ts.frozenStubs.Invalidate(threadID)\n\t\t\t\tfrozen = nil // fall through to trigger check below\n\t\t\t} else {\n\t\t\t\t// Use frozen prefix + fresh tail\n\t\t\t\tcombined := make([]any, 0, len(frozen.Messages)+len(freshMessages))\n\t\t\t\tcombined = append(combined, frozen.Messages...)\n\t\t\t\tcombined = append(combined, freshMessages...)\n\t\t\t\t// Eager-stub large tool_results in fresh tail (model already processed them)\n\t\t\t\tbeforeEager := s.countMessageTokens(combined[len(frozen.Messages):])\n\t\t\t\tcombined = EagerStubToolResults(combined, len(frozen.Messages), s.countTokens)\n\t\t\t\tafterEager := s.countMessageTokens(combined[len(frozen.Messages):])\n\t\t\t\tif beforeEager != afterEager {\n\t\t\t\t\ts.logger.Printf(\"[req %d %s tid=%s] EAGER-STUB: fresh %dk → %dk (saved %dk)\", reqIdx, proj, threadID, beforeEager/1000, afterEager/1000, (beforeEager-afterEager)/1000)\n\t\t\t\t}\n\t\t\t\treq[\"messages\"] = combined\n\t\t\t\tneedsReserialization = true\n\t\t\t\t// Strip embedded CC breakpoints from frozen stubs — they waste cache slots.\n\t\t\t\t// InjectFrozenStubCacheBreakpoint adds exactly one below.\n\t\t\t\tif n := StripMessagesCacheControl(req, 0, len(frozen.Messages)); n > 0 {\n\t\t\t\t\ts.logger.Printf(\"[req %d %s tid=%s] SAWTOOTH: stripped %d embedded breakpoints from frozen stubs\", reqIdx, proj, threadID, n)\n\t\t\t\t}\n\t\t\t\ts.logger.Printf(\"%s[req %d %s tid=%s] SAWTOOTH: frozen prefix (%d stubs, %dk msg) + %d fresh (%dk msg) + %dk overhead = %dk total%s\",\n\t\t\t\t\tcolorGreen, reqIdx, proj, threadID, len(frozen.Messages), frozen.Tokens/1000, len(freshMessages), freshTokens/1000, overhead/1000, combinedTokens/1000, colorReset)\n\t\t\t\trawMsgTokens := s.countMessageTokens(messages)\n\t\t\t\ts.setRawEstimate(threadID, rawMsgTokens+overhead)\n\t\t\t\ts.logger.Printf(\"[req %d %s tid=%s] RAW ESTIMATE: msgTokens=%dk + overhead=%dk = %dk (messages=%d)\", reqIdx, proj, threadID, rawMsgTokens/1000, overhead/1000, (rawMsgTokens+overhead)/1000, len(messages))\n\t\t\t\tif InjectFrozenStubCacheBreakpoint(req, len(frozen.Messages)) {\n\t\t\t\t\ts.logger.Printf(\"[req %d %s tid=%s] SAWTOOTH: frozen stub cache breakpoint at messages[%d]\",\n\t\t\t\t\t\treqIdx, proj, threadID, len(frozen.Messages)-1)\n\t\t\t\t}\n\n\t\t\t\t// Ensure frozen stubs are persisted (may have failed on first request after deploy)\n\t\t\t\ts.ensureFrozenPersisted(threadID)\n\t\t\t}\n\t\t}\n\t\tif frozen == nil {\n\t\t\t// No frozen stubs — check trigger\n\t\t\ttriggerReason := s.sawtoothTrigger.ShouldTrigger(threadID, totalTokens)\n\t\t\tif triggerReason != TriggerNone {\n\t\t\t\ts.logger.Printf(\"[req %d %s tid=%s] SAWTOOTH stub-cycle triggered: %s (tokens=%dk)\",\n\t\t\t\t\treqIdx, proj, threadID, triggerReason, totalTokens/1000)\n\n\t\t\t\t// Refresh briefing during stub-cycle — disabled (briefing now via SessionStart hook)\n\t\t\t\t// s.refreshBriefing(proj)\n\t\t\t\t// if s.briefingText != \"\" {\n\t\t\t\t// \tUpsertSystemBlockCached(req, \"yesmem-briefing\", s.briefingText)\n\t\t\t\t// }\n\n\t\t\t\t// Run full stub pipeline\n\t\t\t\ts.setRawEstimate(threadID, totalTokens)\n\t\t\t\t_ = s.runStubCycle(messages, req, reqIdx, proj, threadID, overhead, totalTokens, userQuery, isRetryReq)\n\n\t\t\t\t// Freeze the result\n\t\t\t\tfinalMessages, _ := req[\"messages\"].([]any)\n\t\t\t\tif finalMessages != nil && len(finalMessages) > 0 {\n\t\t\t\t\t// Boundary = last message of original that maps to last stub\n\t\t\t\t\tcutoff := len(messages)\n\t\t\t\t\tsawtoothCutoff = cutoff\n\t\t\t\t\tvar boundaryMsg any\n\t\t\t\t\tif cutoff > 0 {\n\t\t\t\t\t\tboundaryMsg = messages[cutoff-1]\n\t\t\t\t\t}\n\t\t\t\t\tfrozenTokens := s.countMessageTokens(finalMessages)\n\t\t\t\t\ts.frozenStubs.Store(threadID, finalMessages, cutoff, boundaryMsg, frozenTokens, totalTokens)\n\t\t\t\t\ts.decay.Persist(threadID)\n\t\t\t\t\ts.logger.Printf(\"[req %d %s tid=%s] SAWTOOTH: frozen %d messages at cutoff=%d (~%dk tokens)\",\n\t\t\t\t\t\treqIdx, proj, threadID, len(finalMessages), cutoff, frozenTokens/1000)\n\t\t\t\t\tif InjectFrozenStubCacheBreakpoint(req, len(finalMessages)) {\n\t\t\t\t\t\ts.logger.Printf(\"[req %d %s tid=%s] SAWTOOTH: frozen stub cache breakpoint at messages[%d]\",\n\t\t\t\t\t\t\treqIdx, proj, threadID, len(finalMessages)-1)\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tneedsReserialization = true\n\t\t\t} else {\n\t\t\t\t// No trigger, no frozen stubs — eager-stub to delay first collapse\n\t\t\t\tbeforeEager := s.countMessageTokens(messages)\n\t\t\t\tmessages = EagerStubToolResults(messages, 0, s.countTokens)\n\t\t\t\tafterEager := s.countMessageTokens(messages)\n\t\t\t\tif beforeEager != afterEager {\n\t\t\t\t\ts.logger.Printf(\"[req %d %s tid=%s] EAGER-STUB: %dk → %dk (saved %dk)\", reqIdx, proj, threadID, beforeEager/1000, afterEager/1000, (beforeEager-afterEager)/1000)\n\t\t\t\t\treq[\"messages\"] = messages\n\t\t\t\t\tneedsReserialization = true\n\t\t\t\t}\n\t\t\t\ts.logger.Printf(\"%s[req %d %s tid=%s] %d messages, ~%dk tokens (sawtooth: no trigger)%s\",\n\t\t\t\t\tcolorDim, reqIdx, proj, threadID, len(messages), totalTokens/1000, colorReset)\n\t\t\t}\n\t\t}\n\t} else {\n\t\t// Legacy path: progressive decay\n\t\tif s.shouldStub(totalTokens, model) {\n\t\t\ts.setRawEstimate(threadID, totalTokens)\n\t\t\t_ = s.runStubCycle(messages, req, reqIdx, proj, threadID, overhead, totalTokens, userQuery, isRetryReq)\n\t\t\tneedsReserialization = true\n\t\t}\n\t}\n\n\t// Persistent timestamp injection: re-injects stored timestamps on ALL previous user messages.\n\t// msg:N is computed from position (counting user messages in raw array), NOT from a counter.\n\t// The injected data is deterministic (same bytes every turn) = cache-safe after initial miss.\n\tif threadID != \"\" {\n\t\tif currentMsgs, ok := req[\"messages\"].([]any); ok && len(currentMsgs) > 0 {\n\t\t\ts.timestampStore.Load(threadID)\n\n\t\t\t// Offset = cutoff - stubsCount: maps combined array index to raw position.\n\t\t\t// Raw position of message at combined index I = cutoff + (I - stubs).\n\t\t\t// msgN = rawPos + 1 = cutoff + (I - stubs) + 1 = (cutoff - stubs) + I + 1.\n\t\t\tmsgOffset := sawtoothCutoff - sawtoothStubs\n\n\t\t\ts.logger.Printf(\"[req %d %s] timestamps: rawTotal=%d, postSawtooth=%d, cutoff=%d, stubs=%d, offset=%d\", reqIdx, proj, rawMsgCount, len(currentMsgs), sawtoothCutoff, sawtoothStubs, msgOffset)\n\n\t\t\t// Pre-store assistant timestamp BEFORE inject loop so it's available immediately\n\t\t\ts.responseTsMu.RLock()\n\t\t\trespTime, hasRespTime := s.responseTimes[threadID]\n\t\t\ts.responseTsMu.RUnlock()\n\t\t\tif hasRespTime {\n\t\t\t\trespStr := shortWeekday(respTime.Weekday()) + \" \" + respTime.Format(\"2006-01-02 15:04:05\")\n\t\t\t\tfor j := len(currentMsgs) - 2; j >= 0; j-- {\n\t\t\t\t\tprevMsg, _ := currentMsgs[j].(map[string]any)\n\t\t\t\t\tif prevMsg == nil || prevMsg[\"role\"] != \"assistant\" {\n\t\t\t\t\t\tcontinue\n\t\t\t\t\t}\n\t\t\t\t\tif !msgHasTextBlock(prevMsg) {\n\t\t\t\t\t\ts.logger.Printf(\"[req %d %s] timestamps: skip assistant[%d] no text block\", reqIdx, proj, j)\n\t\t\t\t\t\tcontinue // skip tool-use-only assistant messages\n\t\t\t\t\t}\n\t\t\t\t\tprevMsgN := msgOffset + j + 1\n\t\t\t\t\tif _, exists := s.timestampStore.Get(threadID, prevMsgN); !exists {\n\t\t\t\t\t\ts.timestampStore.Store(threadID, prevMsgN, &TimestampMeta{Timestamp: respStr})\n\t\t\t\t\t\ts.logger.Printf(\"[req %d %s] timestamps: stored assistant msg:%d ts=%s\", reqIdx, proj, prevMsgN, respStr)\n\t\t\t\t\t} else {\n\t\t\t\t\t\ts.logger.Printf(\"[req %d %s] timestamps: assistant msg:%d already has ts\", reqIdx, proj, prevMsgN)\n\t\t\t\t\t}\n\t\t\t\t\tbreak\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\ts.logger.Printf(\"[req %d %s] timestamps: no responseTimes for thread\", reqIdx, proj)\n\t\t\t}\n\n\t\t\t// Inject [msg:N] (+ timestamp/delta if stored) on all PREVIOUS messages\n\t\t\tif n := InjectTimestamps(s.timestampStore, threadID, currentMsgs, len(currentMsgs)-1, msgOffset, sawtoothStubs); n > 0 {\n\t\t\t\tneedsReserialization = true\n\t\t\t\ts.logger.Printf(\"[req %d %s] timestamps: injected %d\", reqIdx, proj, n)\n\t\t\t}\n\n\t\t\t// Current (last) user message: full timestamp + delta + msg:N from raw count\n\t\t\tlastMsg, _ := currentMsgs[len(currentMsgs)-1].(map[string]any)\n\t\t\tif lastMsg != nil && lastMsg[\"role\"] == \"user\" && !msgHasMetaPrefix(lastMsg) {\n\t\t\t\tnow := time.Now()\n\t\t\t\tnowStr := shortWeekday(now.Weekday()) + \" \" + now.Format(\"2006-01-02 15:04:05\")\n\t\t\t\tentry := &TimestampMeta{Timestamp: nowStr}\n\t\t\t\tif hasRespTime {\n\t\t\t\t\tentry.Delta = formatDelta(now.Sub(respTime))\n\t\t\t\t}\n\t\t\t\ts.timestampStore.Store(threadID, rawMsgCount, entry)\n\t\t\t\tprependMeta(lastMsg, BuildMeta(rawMsgCount, entry))\n\t\t\t\tneedsReserialization = true\n\t\t\t}\n\n\t\t\ts.timestampStore.Persist(threadID)\n\t\t}\n\t}\n\n\t// Associative context injection (for ALL requests, not just stubbed ones)\n\t// Re-enabled with higher thresholds (55/75, max 1) after disabling Fresh Remember.\n\tassocContext := s.findAssociativeContextFor(userQuery, proj, threadID, messages)\n\tif assocContext != \"\" {\n\t\tcurrentMessages, _ := req[\"messages\"].([]any)\n\t\tif currentMessages == nil {\n\t\t\tcurrentMessages = messages\n\t\t}\n\t\treq[\"messages\"] = injectAssociativeContext(currentMessages, assocContext, s.cfg.SawtoothEnabled)\n\t\tneedsReserialization = true\n\t\ts.logger.Printf(\"%s[req %d %s tid=%s] associative context injected%s\", colorBlue, reqIdx, proj, threadID, colorReset)\n\t}\n\n\t// Doc-chunk context injection: separate search path for indexed docs (not skills)\n\tdocContext := s.findDocContextFor(userQuery, proj, messages)\n\tif docContext != \"\" {\n\t\tcurrentMessages, _ := req[\"messages\"].([]any)\n\t\tif currentMessages == nil {\n\t\t\tcurrentMessages = messages\n\t\t}\n\t\treq[\"messages\"] = injectAssociativeContext(currentMessages, docContext, s.cfg.SawtoothEnabled)\n\t\tneedsReserialization = true\n\t\ts.logger.Printf(\"%s[req %d %s tid=%s] doc context injected%s\", colorBlue, reqIdx, proj, threadID, colorReset)\n\t}\n\n\t// Fresh Remember Injection: DISABLED\n\t// Causes echo-loop: Claude saves via remember(), sees it again next turn as\n\t// [yesmem fresh memory], reacts to own output instead of user's request.\n\t// Recovery after /clear is handled by recovery.go (briefing), not here.\n\t// Subagents get their own briefing. No remaining use-case.\n\t// remItems := s.popRecentRemember()\n\tvar remItems []recentLearningItem\n\n\t// Rules re-injection: inject condensed CLAUDE.md rules every ~40k output tokens\n\tif rulesBlock := s.rulesInject(threadID, totalTokens, proj); rulesBlock != \"\" {\n\t\tcurrentMessages, _ := req[\"messages\"].([]any)\n\t\tif currentMessages == nil {\n\t\t\tcurrentMessages = messages\n\t\t}\n\t\treq[\"messages\"] = injectAssociativeContext(currentMessages, s.formatRulesReminder(rulesBlock, proj), s.cfg.SawtoothEnabled)\n\t\tneedsReserialization = true\n\t\ts.logger.Printf(\"%s[req %d %s tid=%s] rules reminder injected%s\", colorBlue, reqIdx, proj, threadID, colorReset)\n\t}\n\n\t// Plan auto-activation: nudge Claude to call set_plan() when reading a plan file\n\tif planFile := detectPlanFileRead(messages); planFile != \"\" {\n\t\tif activePlan, _ := s.getActivePlan(threadID); shouldNudgePlan(planFile, activePlan) {\n\t\t\tAppendSystemBlock(req, \"plan-nudge\",\n\t\t\t\tfmt.Sprintf(\"Du hast einen Implementierungsplan gelesen (%s). Aktiviere ihn jetzt via set_plan() damit Plan-Checkpoints funktionieren und dein Fortschritt getrackt wird.\", filepath.Base(planFile)))\n\t\t\tneedsReserialization = true\n\t\t\ts.logger.Printf(\"%s[req %d %s tid=%s] plan nudge injected for %s%s\", colorBlue, reqIdx, proj, threadID, filepath.Base(planFile), colorReset)\n\t\t}\n\t}\n\n\t// Plan checkpoint: inject plan reminder every ~20k tokens if active plan exists\n\ts.detectPlanToolCall(messages, threadID, totalTokens)\n\tif checkpoint := s.planCheckpointInject(threadID, totalTokens); checkpoint != \"\" {\n\t\tcurrentMessages, _ := req[\"messages\"].([]any)\n\t\tif currentMessages == nil {\n\t\t\tcurrentMessages = messages\n\t\t}\n\t\treq[\"messages\"] = injectAssociativeContext(currentMessages, checkpoint, s.cfg.SawtoothEnabled)\n\t\tneedsReserialization = true\n\t\ts.logger.Printf(\"%s[req %d %s tid=%s] plan checkpoint injected%s\", colorBlue, reqIdx, proj, threadID, colorReset)\n\t}\n\n\t// Docs hint injection: inject docs-available reminder every ~10k tokens (independent of plan)\n\tif docsHint := s.docsHintInject(threadID, totalTokens, proj); docsHint != \"\" {\n\t\tcurrentMessages, _ := req[\"messages\"].([]any)\n\t\tif currentMessages == nil {\n\t\t\tcurrentMessages = messages\n\t\t}\n\t\treq[\"messages\"] = injectAssociativeContext(currentMessages, docsHint, s.cfg.SawtoothEnabled)\n\t\tneedsReserialization = true\n\t\ts.logger.Printf(\"%s[req %d %s tid=%s] docs hint injected%s\", colorBlue, reqIdx, proj, threadID, colorReset)\n\t}\n\tif len(remItems) > 0 {\n\t\tvar lines []string\n\t\tfor _, item := range remItems {\n\t\t\tif item.ID > 0 {\n\t\t\t\tlines = append(lines, fmt.Sprintf(\"- [ID:%d] %s\", item.ID, truncateStr(item.Text, 200)))\n\t\t\t} else {\n\t\t\t\tlines = append(lines, \"- \"+truncateStr(item.Text, 200))\n\t\t\t}\n\t\t}\n\t\tremContext := fmt.Sprintf(\"[yesmem fresh memory]\\nGerade gelernt:\\n%s\\n%s\\n[/yesmem fresh memory]\", strings.Join(lines, \"\\n\"), InlineReflectionHint)\n\t\tcurrentMessages, _ := req[\"messages\"].([]any)\n\t\tif currentMessages == nil {\n\t\t\tcurrentMessages = messages\n\t\t}\n\t\treq[\"messages\"] = injectAssociativeContext(currentMessages, remContext, s.cfg.SawtoothEnabled)\n\t\tneedsReserialization = true\n\t\ts.logger.Printf(\"%s[req %d %s tid=%s] fresh remember injected (%d items)%s\", colorBlue, reqIdx, proj, threadID, len(remItems), colorReset)\n\t}\n\n\t// Skill eval injection: mandatory evaluation instruction for real user input only\n\tif threadID != \"\" && proj != \"\" && isUserInputTurn(messages) {\n\t\t// First: detect any skill activations from message history (updates tracker)\n\t\ts.syncSkillActivations(messages, proj, threadID)\n\n\t\t// Inject mandatory skill evaluation block into last user message\n\t\tskillEval := buildSkillEvalBlock()\n\t\tcurrentMessages, _ := req[\"messages\"].([]any)\n\t\tif currentMessages == nil {\n\t\t\tcurrentMessages = messages\n\t\t}\n\t\treq[\"messages\"] = injectAssociativeContext(currentMessages, skillEval, s.cfg.SawtoothEnabled)\n\t\tneedsReserialization = true\n\t\ts.logger.Printf(\"%s[req %d %s tid=%s] skill-eval injected%s\", colorBlue, reqIdx, proj, threadID, colorReset)\n\t}\n\n\t// === Collect injected learning IDs for next turn's inline reflection ===\n\tif threadID != \"\" {\n\t\tcurrentIDs := make(map[int64]string) // id → source\n\t\t// Source 1: Briefing learnings (cached, same every turn)\n\t\ts.briefingMu.RLock()\n\t\tbriefingSnapshot := s.briefingText\n\t\ts.briefingMu.RUnlock()\n\t\tif briefingSnapshot != \"\" {\n\t\t\tfor _, m := range idPattern.FindAllStringSubmatch(briefingSnapshot, -1) {\n\t\t\t\tif id, err := strconv.ParseInt(m[1], 10, 64); err == nil {\n\t\t\t\t\tcurrentIDs[id] = \"briefing\"\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\t// Source 2: Associative context (contextual, per-turn)\n\t\tif assocContext != \"\" {\n\t\t\tfor _, m := range idPattern.FindAllStringSubmatch(assocContext, -1) {\n\t\t\t\tif id, err := strconv.ParseInt(m[1], 10, 64); err == nil {\n\t\t\t\t\tcurrentIDs[id] = \"associative\" // overwrites briefing if both — associative is the stronger signal\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\t// Source 3: Fresh remember items (just learned)\n\t\tfor _, item := range remItems {\n\t\t\tif item.ID > 0 {\n\t\t\t\tcurrentIDs[item.ID] = \"fresh\"\n\t\t\t}\n\t\t}\n\t\ts.lastInjectedIDsMu.Lock()\n\t\tif len(currentIDs) > 0 {\n\t\t\t// Accumulate IDs across turns — don't overwrite.\n\t\t\t// Claude may reference an ID from turn 30 in turn 50.\n\t\t\texisting := s.lastInjectedIDs[threadID]\n\t\t\tif existing == nil {\n\t\t\t\texisting = make(map[int64]string)\n\t\t\t}\n\t\t\tfor id, source := range currentIDs {\n\t\t\t\texisting[id] = source\n\t\t\t}\n\t\t\ts.lastInjectedIDs[threadID] = existing\n\t\t}\n\t\ts.lastInjectedIDsMu.Unlock()\n\t}\n\n\t// Think reminder injection — only for real user turns, not tool continuations\n\tif threadID != \"\" {\n\t\tcurrentMessages, _ := req[\"messages\"].([]any)\n\t\tif currentMessages == nil {\n\t\t\tcurrentMessages = messages\n\t\t}\n\t\tif lastUserHasText(currentMessages) {\n\t\t\tthinkReminder := s.buildThinkReminder(threadID, threadID, false)\n\t\t\tif thinkReminder != \"\" {\n\t\t\t\treq[\"messages\"] = injectAssociativeContext(currentMessages, thinkReminder, s.cfg.SawtoothEnabled)\n\t\t\t\tneedsReserialization = true\n\t\t\t}\n\t\t}\n\n\t\t// Briefing injection: prepend user/assistant turn pair at beginning of messages.\n\t\t// Static per session → stable prefix → cacheable. Must be before dialog injection.\n\t\tif s.injectBriefingTurn(req, reqIdx, proj, threadID) {\n\t\t\tneedsReserialization = true\n\t\t}\n\n\t\t// Code Map injection: insert after briefing turn pair.\n\t\tif s.injectCodeMapTurn(req, reqIdx, proj, threadID) {\n\t\t\tneedsReserialization = true\n\t\t}\n\n\t\t// Dialog injection: append at end of messages with pseudo-padding for alternation.\n\t\t// Always works regardless of tool_result chains or conversation state.\n\t\tdr := s.checkDialogMessages(threadID, proj)\n\t\tif dr.Extra != \"\" {\n\t\t\t// Prepend session ID for agent-to-agent communication\n\t\t\tdr.Extra = fmt.Sprintf(\"DEINE_SESSION_ID: %s\\n\", threadID) + dr.Extra\n\t\t\tinjected := false\n\t\t\tif msgs, ok := req[\"messages\"].([]any); ok && len(msgs) > 0 {\n\t\t\t\t// Pad with empty assistant if last message is user (maintains alternation)\n\t\t\t\tlastMsg, _ := msgs[len(msgs)-1].(map[string]any)\n\t\t\t\tif lastMsg != nil && lastMsg[\"role\"] == \"user\" {\n\t\t\t\t\tmsgs = append(msgs, map[string]any{\n\t\t\t\t\t\t\"role\": \"assistant\",\n\t\t\t\t\t\t\"content\": \"​\", // zero-width space — invisible padding\n\t\t\t\t\t})\n\t\t\t\t}\n\t\t\t\t// Append dialog as new user message (becomes the last message Claude sees)\n\t\t\t\tmsgs = append(msgs, map[string]any{\n\t\t\t\t\t\"role\": \"user\",\n\t\t\t\t\t\"content\": \"\\n\" + dr.Extra + \"\\n\",\n\t\t\t\t})\n\t\t\t\treq[\"messages\"] = msgs\n\t\t\t\tneedsReserialization = true\n\t\t\t\tinjected = true\n\t\t\t}\n\t\t\t// Mark as read after 3 injection turns — long enough for Claude to process,\n\t\t\t// resilient against fast ping-pong sessions (time-based was broken for <30s turns).\n\t\t\tif injected && dr.HasUnread && dr.SessionID != \"\" {\n\t\t\t\tif s.shouldMarkChannelRead(dr) {\n\t\t\t\t\ts.markDialogRead(dr)\n\t\t\t\t}\n\t\t\t}\n\t\t\ts.logger.Printf(\"[dialog] injection injected=%v extra_len=%d\", injected, len(dr.Extra))\n\t\t}\n\t}\n\n\t// Signal tools are NOT injected into the main request.\n\t// They are collected via a separate async reflection call after the response.\n\t// See fireReflectionCall() in reflection.go.\n\t// NEVER put _signal_* tools back here — the model ignores them among 44+ real tools.\n\n\t// Legacy assistant annotation: timestamp on last assistant message after cache breakpoint.\n\t// User timestamps are now handled by TimestampStore (injected earlier in the pipeline).\n\tif threadID != \"\" {\n\t\tif msgs, ok := req[\"messages\"].([]any); ok && len(msgs) > 0 {\n\t\t\t// Find last cache_control breakpoint in messages\n\t\t\tcacheIdx := -1\n\t\t\tfor i, m := range msgs {\n\t\t\t\tmsg, _ := m.(map[string]any)\n\t\t\t\tif msg == nil {\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\t\t\t\tif _, has := msg[\"cache_control\"]; has {\n\t\t\t\t\tcacheIdx = i\n\t\t\t\t}\n\t\t\t\tif content, ok := msg[\"content\"].([]any); ok {\n\t\t\t\t\tfor _, block := range content {\n\t\t\t\t\t\tif b, ok := block.(map[string]any); ok {\n\t\t\t\t\t\t\tif _, has := b[\"cache_control\"]; has {\n\t\t\t\t\t\t\t\tcacheIdx = i\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t\tstartIdx := cacheIdx + 1\n\t\t\tif startIdx < 0 {\n\t\t\t\tstartIdx = 0\n\t\t\t}\n\t\t\ts.responseTsMu.RLock()\n\t\t\trespTime, hasRespTime := s.responseTimes[threadID]\n\t\t\ts.responseTsMu.RUnlock()\n\t\t\tif hasRespTime {\n\t\t\t\tfor i := len(msgs) - 1; i >= startIdx; i-- {\n\t\t\t\t\tmsg, _ := msgs[i].(map[string]any)\n\t\t\t\t\tif msg != nil && msg[\"role\"] == \"assistant\" {\n\t\t\t\t\t\tmeta := fmt.Sprintf(\"[%s] [msg:%d]\", respTime.Format(\"2006-01-02 15:04:05\"), i)\n\t\t\t\t\t\tprependMeta(msg, meta)\n\t\t\t\t\t\tneedsReserialization = true\n\t\t\t\t\t\tbreak\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\t// Self-Priming: DISABLED (experimental, see signal_handlers.go comment block).\n\t// The reflection-based selfprime (Haiku) was too inaccurate to be useful —\n\t// the main model adapts to user context organically from message history.\n\t// Kept for reference; re-enable by uncommenting below + setting ShouldActivate=true.\n\t//\n\t// s.selfPrimeMu.RLock()\n\t// anchor := s.selfPrimes[threadID]\n\t// s.selfPrimeMu.RUnlock()\n\t// if anchor != \"\" {\n\t// \tif msgs, ok := req[\"messages\"].([]any); ok && len(msgs) > 0 {\n\t// \t\tlastMsg, _ := msgs[len(msgs)-1].(map[string]any)\n\t// \t\tif lastMsg != nil && lastMsg[\"role\"] == \"user\" {\n\t// \t\t\tprimeBlock := map[string]any{\n\t// \t\t\t\t\"type\": \"text\",\n\t// \t\t\t\t\"text\": \"[self-prime]\\n[Self-Prime von deiner letzten Antwort]: \" + anchor,\n\t// \t\t\t}\n\t// \t\t\tif content, ok := lastMsg[\"content\"].([]any); ok {\n\t// \t\t\t\tlastMsg[\"content\"] = append(content, primeBlock)\n\t// \t\t\t} else if text, ok := lastMsg[\"content\"].(string); ok {\n\t// \t\t\t\tlastMsg[\"content\"] = []any{\n\t// \t\t\t\t\tmap[string]any{\"type\": \"text\", \"text\": text},\n\t// \t\t\t\t\tprimeBlock,\n\t// \t\t\t\t}\n\t// \t\t\t}\n\t// \t\t\tneedsReserialization = true\n\t// \t\t}\n\t// \t}\n\t// }\n\n\t// Prompt Caching: inject cache_control breakpoints on system + tools\n\t// Sawtooth: disabled (all 4 slots are used by CC system (2) + briefing (1) + CC messages (1))\n\t// Legacy: count existing breakpoints and fill up remaining slots.\n\tif !s.cfg.SawtoothEnabled && s.cacheGate.ShouldCache() {\n\t\tif n := InjectCacheBreakpoints(req, s.logger); n > 0 {\n\t\t\tneedsReserialization = true\n\t\t\ts.logger.Printf(\"[req %d %s] prompt cache: %d breakpoints injected\", reqIdx, proj, n)\n\t\t} else {\n\t\t\ts.logger.Printf(\"[req %d %s] prompt cache: no budget (4/4 already set)\", reqIdx, proj)\n\t\t}\n\t}\n\n\t// Final: upgrade cache TTL + enforce breakpoint limit (must run AFTER all injections + InjectCacheBreakpoints)\n\t// Shift the last messages-breakpoint from unstable user message to stable assistant message.\n\t// Text user messages change between turns (timestamps, reminders, structure); assistant messages don't.\n\t// Tool-result messages are excluded — they are stable cache anchors.\n\tif ShiftMessageBreakpoint(req) {\n\t\tneedsReserialization = true\n\t}\n\t// TTL upgrade only for API key auth — subscription (OAuth) users should not get extended TTL.\n\tif s.cfg.CacheTTL != \"\" && s.cfg.CacheTTL != \"ephemeral\" && IsAPIKeyAuth(r.Header) {\n\t\tif n := UpgradeCacheTTL(req, s.cfg.CacheTTL); n > 0 {\n\t\t\tneedsReserialization = true\n\t\t\ts.logger.Printf(\"[req %d %s] cache TTL upgraded: %d blocks → %s\", reqIdx, proj, n, s.cfg.CacheTTL)\n\t\t}\n\t}\n\tif n := EnforceCacheBreakpointLimit(req, maxCacheBreakpoints); n > 0 {\n\t\tneedsReserialization = true\n\t\ts.logger.Printf(\"[req %d %s] prompt cache: trimmed %d surplus breakpoints\", reqIdx, proj, n)\n\t}\n\n\t// Pre-flight: validate tool_use/tool_result pairing before forwarding.\n\t// Repairs orphans created by Stubify/Collapse/Injection pipeline.\n\tif msgs, ok := req[\"messages\"].([]any); ok {\n\t\trepaired, orphans := validateToolPairs(msgs, s.logger)\n\t\tif orphans > 0 {\n\t\t\treq[\"messages\"] = repaired\n\t\t\ts.logger.Printf(\"%s[req %d %s tid=%s] validate: repaired %d orphan tool_result(s)%s\", colorOrange, reqIdx, proj, threadID, orphans, colorReset)\n\t\t\tneedsReserialization = true\n\t\t}\n\t}\n\n\t// Early-out: skip re-serialization when nothing changed\n\tif !needsReserialization {\n\t\ts.logger.Printf(\"%s[req %d %s tid=%s] %d messages, ~%dk tokens (no stubbing, threshold=%dk)%s\", colorDim, reqIdx, proj, threadID, len(messages), totalTokens/1000, s.effectiveTokenThreshold(model)/1000, colorReset)\n\t\ts.forwardWithAnnotation(w, r, body, reqIdx, toolUseIDs, proj, threadID, msgCount, totalTokens)\n\t\treturn\n\t}\n\n\t// Re-serialize\n\tbody, err = json.Marshal(req)\n\tif err != nil {\n\t\ts.logger.Printf(\"%s[req %d %s tid=%s] re-marshal error: %v%s\", colorRed, reqIdx, proj, threadID, err, colorReset)\n\t\thttp.Error(w, \"internal proxy error\", http.StatusInternalServerError)\n\t\treturn\n\t}\n\n\t// Debug: dump final request body to file (enable with YESMEM_PROXY_DEBUG=1)\n\tif needsReserialization && os.Getenv(\"YESMEM_PROXY_DEBUG\") == \"1\" {\n\t\tdumpDir := filepath.Join(s.cfg.DataDir, \"debug\")\n\t\tos.MkdirAll(dumpDir, 0755)\n\t\tts := time.Now().Format(\"20060102-150405\")\n\t\tdumpPath := fmt.Sprintf(\"%s/req_%s_%d.json\", dumpDir, ts, reqIdx)\n\t\tos.WriteFile(dumpPath, body, 0644)\n\t\ts.logger.Printf(\"[req %d] dumped %dk to %s\", reqIdx, len(body)/1024, dumpPath)\n\t}\n\n\t// Task #7: Mark this request fingerprint for retry detection\n\tif !isRetryReq {\n\t\ts.markRequest(fp)\n\t}\n\n\t// Forward to Anthropic with SSE annotation extraction\n\t// Use original msgCount (pre-injection) so actual-based estimation stays consistent across turns\n\t// Use actual-based totalTokens as estimate when available (more accurate than full recount)\n\tfinalEstimate := totalTokens\n\tif s.sawtoothTrigger == nil || s.sawtoothTrigger.GetLastTokens(threadID) == 0 {\n\t\t// First request: no actual yet, do a full recount of post-injection messages\n\t\tfinalMsgs, _ := req[\"messages\"].([]any)\n\t\tfinalEstimate = s.measureOverhead(req) + s.countMessageTokens(finalMsgs)\n\t}\n\ts.logger.Printf(\"[req %d %s tid=%s] FINAL: ~%dk estimated (after all injections)\", reqIdx, proj, threadID, finalEstimate/1000)\n\tif raw := s.getRawEstimate(threadID); raw > 0 {\n\t\ts.cacheStatusWriter.UpdateRawForThread(threadID, raw)\n\t}\n\ts.forwardWithAnnotation(w, r, body, reqIdx, toolUseIDs, proj, threadID, msgCount, finalEstimate)\n}\n\n// bytesPerToken is the approximation factor for token estimation.\n// Anthropic recommends ~3.5 chars/token; we use 3.6 for slight overestimation\n// (triggers stubbing a bit earlier = safer for context budget).\nconst bytesPerToken = 3.6\n\n// TokenEstimateFunc is the central token estimation function.\n// All stubbing/budget decisions use this. Receives extracted text content\n// (not JSON wire format) so the tokenizer counts actual content tokens.\ntype TokenEstimateFunc func(text string) int\n"}, "af6c88ef65a6c51137a2f1cb47d34aaac47d7057": {"t": 1785759364.5160124, "n": 58344, "h": "7b4721a1ea812523", "c": "package proxy\n\nimport (\n\t\"context\"\n\t\"crypto/tls\"\n\t\"encoding/json\"\n\t\"fmt\"\n\t\"io\"\n\t\"log\"\n\t\"net\"\n\t\"net/http\"\n\t\"os\"\n\t\"os/signal\"\n\t\"path/filepath\"\n\t\"strconv\"\n\t\"strings\"\n\t\"sync\"\n\t\"sync/atomic\"\n\t\"syscall\"\n\t\"time\"\n\n\ttokenizer \"github.com/qhenkart/anthropic-tokenizer-go\"\n)\n\n// Config holds proxy configuration.\ntype Config struct {\n\tListenAddr string // e.g. \":9099\"\n\tTargetURL string // e.g. \"https://api.anthropic.com\"\n\tTokenThreshold int // trigger stubbing above this estimated token count\n\tTokenMinimumThreshold int // stub down to this floor\n\tTokenThresholds map[string]int // model-specific thresholds: {\"opus\": 180000, \"haiku\": 130000}\n\tKeepRecent int // number of messages to always keep unmodified\n\tDataDir string // yesmem data directory for DB access\n\tOpenAITargetURL string // upstream for OpenAI-format clients; if empty, uses TargetURL\n\n\t// Signal reflection\n\tSignalsEnabled bool // enable async signal reflection calls\n\tSignalsMode string // \"reflection\" = separate API call\n\tSignalsEveryNTurns int // every N end_turn responses\n\tSignalsModel string // full model ID, e.g. \"claude-sonnet-4-6\"\n\tAPIKey string // needed for reflection API calls\n\n\t// Sawtooth cache optimization\n\tSawtoothEnabled bool // use sawtooth instead of progressive decay (default: true)\n\tCacheTTL string // \"ephemeral\" (5m) or \"1h\" (extended)\n\n\t// Usage deflation: scale down input_tokens reported to CC to suppress \"Context low\" warning.\n\t// 0 = disabled, 0.7 = report 70% of actual tokens. Real values kept for internal tracking.\n\tUsageDeflationFactor float64\n\n\t// System prompt rewriting\n\tPromptUngate bool // strip \"may or may not be relevant\" disclaimer from CLAUDE.md injection\n\tPromptRewrite bool // strip \"Output efficiency\" + \"short and concise\", inject Ant-quality directives\n\tPromptEnhance bool // CLAUDE.md authority reinforcement, comment discipline, persona-based tone\n\tEffortFloor string // minimum effort level: \"low\", \"medium\", \"high\", \"max\" (empty = off)\n\n\t// Cache keepalive\n\tCacheKeepaliveEnabled bool\n\tCacheKeepaliveMode string // \"auto\", \"5m\", \"1h\"\n\tCacheKeepalivePings5m int\n\tCacheKeepalivePings1h int\n\n\t// Forked agents\n\tForkedAgentsEnabled bool\n\tForkedAgentsModel string // full model ID\n\tForkedAgentsTokenGrowthTrigger int\n\tForkedAgentsMaxFailures int\n\tForkedAgentsMaxForksPerSession int\n\tForkedAgentsDebug bool\n\n\t// Cache state management\n\tResetCache bool // clear persisted frozen stubs and decay state on startup\n}\n\nconst maxAnnotations = 5000 // evict oldest when exceeded\n\n// ANSI color codes for log output\nconst (\n\tcolorReset = \"\\033[0m\"\n\tcolorDim = \"\\033[2m\" // gray — passthrough / no stubbing\n\tcolorGreen = \"\\033[32m\" // stubbed / compressed\n\tcolorLightGreen = \"\\033[92m\" // FINAL without compression\n\tcolorBlue = \"\\033[34m\" // injection\n\tcolorOrange = \"\\033[33m\" // skip / retry\n\tcolorRed = \"\\033[31m\" // errors\n)\n\n// Server is the infinite-thread proxy.\ntype Server struct {\n\tcfg Config\n\thttpClient *http.Client\n\tlogger *log.Logger\n\trequestIdx atomic.Int64\n\n\t// Post-hoc annotations: tool_use_id → first ~120 chars of Claude's response\n\tmu sync.RWMutex\n\tannotations map[string]string\n\n\t// Progressive decay tracker\n\tdecay *DecayTracker\n\n\t// Living narrative block\n\tnarrative *Narrative\n\n\t// Pivot moments cache (from daemon)\n\tpivotMu sync.RWMutex\n\tpivotTexts []string\n\tpivotCached time.Time\n\n\t// Aggregate stats (Task #2)\n\tstats *ProxyStats\n\n\t// Local tokenizer for accurate token counting\n\ttokenizer *tokenizer.Tokenizer\n\n\t// Cached overhead tokens (system + tools), set on first request per thread\n\toverheadMu sync.RWMutex\n\toverheadTokens int // unused, kept for interface compat\n\n\t// Hysteresis state for stubbing (Task #6)\n\tstubActive atomic.Bool\n\n\t// Daemon connection state — first successful connect flips to true\n\tdaemonReady atomic.Bool\n\n\t// Retry detection (Task #7)\n\tretryMu sync.RWMutex\n\tlastFingerprint string\n\n\t// Session start time for temporal annotation in archive blocks\n\tsessionStartMu sync.RWMutex\n\tsessionStartTime time.Time\n\n\t// Runtime config override (set via MCP set_config tool)\n\tconfigOverrideMu sync.RWMutex\n\ttokenThresholdOverrides map[string]int // model-key → threshold, \"\" = global fallback\n\n\t// Briefing cache — loaded once per thread, injected as user/assistant message pair\n\tbriefingMu sync.RWMutex\n\tbriefingText string\n\n\t// Cognitive signal bus — routes _signal_* tool calls to handlers\n\tsignalBus *SignalBus\n\n\t// Inline reflection: track which learning IDs were injected per thread\n\tlastInjectedIDsMu sync.Mutex\n\tlastInjectedIDs map[string]map[int64]string // threadID → id → source (\"briefing\"|\"associative\"|\"fresh\")\n\tsessionInjectCounts map[string]map[int64]int // threadID → learningID → injection count this session\n\tlastTurnInjected map[string]map[int64]bool // threadID → learningIDs injected in previous turn\n\n\t// Self-priming anchors: threadID → last self-prime text\n\tselfPrimeMu sync.RWMutex\n\tselfPrimes map[string]string\n\n\t// Timestamp tracking: threadID → time of last response completion\n\tresponseTsMu sync.RWMutex\n\tresponseTimes map[string]time.Time\n\n\t// Think reminder: per-thread request counter (replaces hook-think file-based counter)\n\tthinkMu sync.Mutex\n\tthinkCounters map[string]int // threadID → request count\n\n\tchannelMu sync.Mutex\n\tchannelInjectCount map[string]int // sessionID → injection turn count\n\n\t// Prompt cache gating — enables cache_control breakpoints when requests are frequent\n\tcacheGate *CacheGate\n\n\t// Sawtooth cache optimization\n\tfrozenStubs *FrozenStubs\n\tsawtoothTrigger *SawtoothTrigger\n\ttimestampStore *TimestampStore\n\tcacheStatusWriter *CacheStatusWriter\n\tcacheKeepalive *CacheKeepalive\n\tcacheTTLDetector *CacheTTLDetector\n\n\t// Cumulative token savings from collapsing (per threadID)\n\trawSavingsMu sync.Mutex\n\trawSavings map[string]int\n\n\t// Skill hint tracking: per-thread active skills\n\tskillTracker *skillHintTracker\n\n\t// Forked agent state: per-thread token growth + failure tracking\n\tforkState *ForkState\n\tforkConfigs []ForkConfig\n\n\t// Rules re-injection: condensed CLAUDE.md rules injected every ~40k tokens\n\trulesMu sync.RWMutex\n\trulesBlock string // cached condensed rules (fetched once from daemon)\n\trulesTokenCount map[string]int // threadID → tokens since last rules injection\n\trulesCollapseInjected map[string]bool // threadID → true if collapse already injected rules (reset by normal inject)\n\tmsgCounters *msgCounters // global per-thread msg counter, persists across collapses\n\n\t// Loop detection: per-thread warning state\n\tloopMu sync.Mutex\n\tloopStates map[string]*LoopState // threadID → state\n}\n\n// Run starts the proxy server and blocks until interrupted.\nfunc Run(cfg Config) error {\n\ts := &Server{\n\t\tcfg: cfg,\n\t\thttpClient: &http.Client{\n\t\t\tTimeout: 10 * time.Minute,\n\t\t\tTransport: &http.Transport{\n\t\t\t\tTLSClientConfig: &tls.Config{},\n\t\t\t\tMaxIdleConns: 10,\n\t\t\t\tIdleConnTimeout: 90 * time.Second,\n\t\t\t\tDisableCompression: true,\n\t\t\t\tResponseHeaderTimeout: 60 * time.Second,\n\t\t\t},\n\t\t},\n\t\tlogger: createLogger(cfg.DataDir),\n\t\tannotations: make(map[string]string),\n\t\tdecay: NewDecayTracker(),\n\t\tnarrative: NewNarrative(),\n\t\tstats: &ProxyStats{startTime: time.Now()},\n\t\tselfPrimes: make(map[string]string),\n\t\tlastInjectedIDs: make(map[string]map[int64]string),\n\t\tsessionInjectCounts: make(map[string]map[int64]int),\n\t\tlastTurnInjected: make(map[string]map[int64]bool),\n\t\tresponseTimes: make(map[string]time.Time),\n\t\tthinkCounters: make(map[string]int),\n\t\tchannelInjectCount: make(map[string]int),\n\t\tcacheGate: NewCacheGate(cacheGapForTTL(cfg.CacheTTL)),\n\t\tfrozenStubs: NewFrozenStubsWithTTL(sawtoothTTLForCacheTTL(cfg.CacheTTL)),\n\t\ttimestampStore: NewTimestampStore(),\n\t\tsawtoothTrigger: NewSawtoothTrigger(cacheGapForTTL(cfg.CacheTTL), cfg.TokenThreshold, cfg.TokenMinimumThreshold),\n\t\tcacheStatusWriter: NewCacheStatusWriter(cfg.DataDir, cfg.CacheTTL, cfg.TokenThreshold, cfg.TokenMinimumThreshold),\n\t\tcacheTTLDetector: NewCacheTTLDetectorWithPersist(cfg.DataDir),\n\t\tskillTracker: newSkillHintTracker(),\n\t\trulesTokenCount: make(map[string]int),\n\t\trulesCollapseInjected: make(map[string]bool),\n\t\tmsgCounters: newMsgCounters(),\n\t\tloopStates: make(map[string]*LoopState),\n\t\tforkState: NewForkState(cfg.ForkedAgentsTokenGrowthTrigger, cfg.ForkedAgentsMaxFailures, cfg.ForkedAgentsMaxForksPerSession),\n\t\tforkConfigs: []ForkConfig{},\n\t}\n\n\t// Log persisted detection state\n\tif sup := s.cacheTTLDetector.Is1hSupported(); sup != nil {\n\t\ts.logger.Printf(\"[proxy] loaded persisted TTL detection: 1h_supported=%v\", *sup)\n\t\tif *sup && s.frozenStubs != nil {\n\t\t\ts.frozenStubs.UpdateTTL(sawtoothTTLForCacheTTL(\"1h\"))\n\t\t}\n\t}\n\n\tif cfg.CacheKeepaliveEnabled {\n\t\ts.cacheKeepalive = NewCacheKeepalive(CacheKeepaliveConfig{\n\t\t\tTarget: cfg.TargetURL,\n\t\t\tMode: cfg.CacheKeepaliveMode,\n\t\t\tPings5m: cfg.CacheKeepalivePings5m,\n\t\t\tPings1h: cfg.CacheKeepalivePings1h,\n\t\t\tDetector: s.cacheTTLDetector,\n\t\t\tLogger: s.logger,\n\t\t\tOnPing: func(threadID string, cacheRead, cacheWrite int) {\n\t\t\t\ts.cacheTTLDetector.RecordResponse(cacheRead, cacheWrite, 0)\n\t\t\t\tif s.frozenStubs != nil {\n\t\t\t\t\ts.frozenStubs.Touch(threadID)\n\t\t\t\t}\n\t\t\t\tif s.sawtoothTrigger != nil {\n\t\t\t\t\ts.sawtoothTrigger.TouchRequestTime(threadID)\n\t\t\t\t}\n\t\t\t},\n\t\t})\n\t}\n\n\t// Wire detector + keepalive to status writer for terminal display\n\ts.cacheStatusWriter.SetDetector(s.cacheTTLDetector)\n\tif s.cacheKeepalive != nil {\n\t\ts.cacheStatusWriter.SetKeepalive(s.cacheKeepalive)\n\t}\n\n\t// Register forked agent configs\n\tif cfg.ForkedAgentsEnabled {\n\t\ts.forkConfigs = append(s.forkConfigs, NewExtractAndEvaluateConfig(cfg.ForkedAgentsModel))\n\t\tmodelDesc := cfg.ForkedAgentsModel\n\t\tif modelDesc == \"\" {\n\t\t\tmodelDesc = \"(same as main thread)\"\n\t\t}\n\t\ts.logger.Printf(\"[proxy] forked agents enabled: %d configs, model=%s, trigger=%dk tokens\",\n\t\t\tlen(s.forkConfigs), modelDesc, cfg.ForkedAgentsTokenGrowthTrigger/1000)\n\t}\n\n\t// Wire sawtooth token persistence via daemon RPC\n\ts.sawtoothTrigger.SetPersistFunc(func(key, value string) {\n\t\tif _, err := s.queryDaemon(\"set_proxy_state\", map[string]any{\"key\": key, \"value\": value}); err != nil {\n\t\t\ts.logger.Printf(\"[sawtooth] persist failed for %s: %v\", key, err)\n\t\t}\n\t})\n\ts.sawtoothTrigger.SetLoadFunc(func(key string) (string, bool) {\n\t\tresult, err := s.queryDaemon(\"get_proxy_state\", map[string]any{\"key\": key})\n\t\tif err != nil || result == nil {\n\t\t\treturn \"\", false\n\t\t}\n\t\tvar resp struct {\n\t\t\tValue string `json:\"value\"`\n\t\t}\n\t\tif json.Unmarshal(result, &resp) != nil || resp.Value == \"\" {\n\t\t\treturn \"\", false\n\t\t}\n\t\treturn resp.Value, true\n\t})\n\n\t// Wire frozen stubs persistence via same daemon RPC\n\ts.frozenStubs.SetPersistFunc(func(key, value string) {\n\t\tif _, err := s.queryDaemon(\"set_proxy_state\", map[string]any{\"key\": key, \"value\": value}); err != nil {\n\t\t\ts.logger.Printf(\"[frozen] persist failed for %s: %v\", key, err)\n\t\t}\n\t})\n\ts.frozenStubs.SetLoadFunc(func(key string) (string, bool) {\n\t\tresult, err := s.queryDaemon(\"get_proxy_state\", map[string]any{\"key\": key})\n\t\tif err != nil || result == nil {\n\t\t\treturn \"\", false\n\t\t}\n\t\tvar resp struct {\n\t\t\tValue string `json:\"value\"`\n\t\t}\n\t\tif json.Unmarshal(result, &resp) != nil || resp.Value == \"\" {\n\t\t\treturn \"\", false\n\t\t}\n\t\treturn resp.Value, true\n\t})\n\n\t// Wire decay tracker persistence via same daemon RPC\n\ts.decay.SetPersistFunc(func(key, value string) {\n\t\tif _, err := s.queryDaemon(\"set_proxy_state\", map[string]any{\"key\": key, \"value\": value}); err != nil {\n\t\t\ts.logger.Printf(\"[decay] persist failed for %s: %v\", key, err)\n\t\t}\n\t})\n\ts.decay.SetLoadFunc(func(key string) (string, bool) {\n\t\tresult, err := s.queryDaemon(\"get_proxy_state\", map[string]any{\"key\": key})\n\t\tif err != nil || result == nil {\n\t\t\treturn \"\", false\n\t\t}\n\t\tvar resp struct {\n\t\t\tValue string `json:\"value\"`\n\t\t}\n\t\tif json.Unmarshal(result, &resp) != nil || resp.Value == \"\" {\n\t\t\treturn \"\", false\n\t\t}\n\t\treturn resp.Value, true\n\t})\n\n\t// Wire timestamp store persistence via same daemon RPC\n\ts.timestampStore.SetPersistFunc(func(key, value string) {\n\t\tif _, err := s.queryDaemon(\"set_proxy_state\", map[string]any{\"key\": key, \"value\": value}); err != nil {\n\t\t\ts.logger.Printf(\"[timestamps] persist failed for %s: %v\", key, err)\n\t\t}\n\t})\n\ts.timestampStore.SetLoadFunc(func(key string) (string, bool) {\n\t\tresult, err := s.queryDaemon(\"get_proxy_state\", map[string]any{\"key\": key})\n\t\tif err != nil || result == nil {\n\t\t\treturn \"\", false\n\t\t}\n\t\tvar resp struct {\n\t\t\tValue string `json:\"value\"`\n\t\t}\n\t\tif json.Unmarshal(result, &resp) != nil || resp.Value == \"\" {\n\t\t\treturn \"\", false\n\t\t}\n\t\treturn resp.Value, true\n\t})\n\n\t// Clear persisted cache state if --reset-cache was passed\n\tif cfg.ResetCache {\n\t\tfor _, prefix := range []string{\"frozen:\", \"decay:\", \"timestamps:\"} {\n\t\t\tresult, err := s.queryDaemon(\"delete_proxy_state_prefix\", map[string]any{\"prefix\": prefix})\n\t\t\tif err != nil {\n\t\t\t\ts.logger.Printf(\"[reset-cache] failed to delete %s*: %v\", prefix, err)\n\t\t\t} else {\n\t\t\t\tvar resp struct{ Deleted int }\n\t\t\t\tjson.Unmarshal(result, &resp)\n\t\t\t\ts.logger.Printf(\"[reset-cache] deleted %d %s* entries\", resp.Deleted, prefix)\n\t\t\t}\n\t\t}\n\t}\n\n\t// Init signal bus with daemon RPC as callback\n\ts.signalBus = NewSignalBus(s.logger)\n\tregisterSignalHandlers(s.signalBus, s.logger, s.queryDaemon, func(threadID, anchor string) {\n\t\ts.selfPrimeMu.Lock()\n\t\ts.selfPrimes[threadID] = anchor\n\t\ts.selfPrimeMu.Unlock()\n\t})\n\n\t// Init local tokenizer (BPE-based, ~60ms)\n\ttok, err := tokenizer.New()\n\tif err != nil {\n\t\ts.logger.Printf(\"%s[proxy] tokenizer init failed, falling back to heuristic: %v%s\", colorOrange, err, colorReset)\n\t} else {\n\t\ts.tokenizer = tok\n\t\ts.logger.Printf(\"[proxy] tokenizer initialized\")\n\t}\n\n\tmux := http.NewServeMux()\n\tmux.HandleFunc(\"/health\", s.handleHealth)\n\tmux.HandleFunc(\"/\", s.handleRequest)\n\n\tsrv := &http.Server{\n\t\tAddr: sanitizeListenAddr(cfg.ListenAddr),\n\t\tHandler: mux,\n\t}\n\n\tdone := make(chan os.Signal, 1)\n\tsignal.Notify(done, syscall.SIGINT, syscall.SIGTERM)\n\n\tgo func() {\n\t\t<-done\n\t\ts.logger.Println(\"shutting down (draining in-flight requests)...\")\n\t\tif s.cacheKeepalive != nil {\n\t\t\ts.cacheKeepalive.Stop()\n\t\t}\n\t\tctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)\n\t\tdefer cancel()\n\t\tif err := srv.Shutdown(ctx); err != nil {\n\t\t\ts.logger.Printf(\"graceful shutdown failed: %v, forcing close\", err)\n\t\t\tsrv.Close()\n\t\t}\n\t}()\n\n\ts.logger.Printf(\"listening on %s → %s (threshold=%d, keepRecent=%d, model_thresholds=%v)\",\n\t\tsanitizeListenAddr(cfg.ListenAddr), cfg.TargetURL, cfg.TokenThreshold, cfg.KeepRecent, cfg.TokenThresholds)\n\n\tif err := srv.ListenAndServe(); err != http.ErrServerClosed {\n\t\treturn fmt.Errorf(\"listen: %w\", err)\n\t}\n\treturn nil\n}\n\n// effectiveTokenThreshold returns the runtime token threshold for a given model.\n// Priority: MCP override (model-specific) > MCP override (global) > config model-specific > config global.\nfunc (s *Server) effectiveTokenThreshold(model string) int {\n\ts.configOverrideMu.RLock()\n\toverrides := s.tokenThresholdOverrides\n\ts.configOverrideMu.RUnlock()\n\tif len(overrides) > 0 && model != \"\" {\n\t\tlower := strings.ToLower(model)\n\t\tfor key, threshold := range overrides {\n\t\t\tif key != \"\" && strings.Contains(lower, strings.ToLower(key)) && threshold > 0 {\n\t\t\t\treturn threshold\n\t\t\t}\n\t\t}\n\t}\n\t// Global override fallback (key \"\")\n\tif len(overrides) > 0 {\n\t\tif globalOverride, ok := overrides[\"\"]; ok && globalOverride > 0 {\n\t\t\treturn globalOverride\n\t\t}\n\t}\n\tif model != \"\" && len(s.cfg.TokenThresholds) > 0 {\n\t\tlower := strings.ToLower(model)\n\t\tfor key, threshold := range s.cfg.TokenThresholds {\n\t\t\tif strings.Contains(lower, strings.ToLower(key)) {\n\t\t\t\treturn threshold\n\t\t\t}\n\t\t}\n\t}\n\treturn s.cfg.TokenThreshold\n}\n\n// SetTokenThresholdOverride sets a runtime override for the token threshold per model key.\n// Pass threshold=0 to reset the override for that key. Key \"\" is the global fallback.\nfunc (s *Server) SetTokenThresholdOverride(modelKey string, threshold int) {\n\ts.configOverrideMu.Lock()\n\tif s.tokenThresholdOverrides == nil {\n\t\ts.tokenThresholdOverrides = make(map[string]int)\n\t}\n\tif threshold <= 0 {\n\t\tdelete(s.tokenThresholdOverrides, modelKey)\n\t} else {\n\t\ts.tokenThresholdOverrides[modelKey] = threshold\n\t}\n\ts.configOverrideMu.Unlock()\n\n\t// Propagate global threshold to sawtooth trigger so runtime overrides take effect\n\tif modelKey == \"\" && s.sawtoothTrigger != nil {\n\t\tif threshold > 0 {\n\t\t\ts.sawtoothTrigger.SetTokenThreshold(threshold)\n\t\t} else {\n\t\t\ts.sawtoothTrigger.SetTokenThreshold(s.cfg.TokenThreshold)\n\t\t}\n\t}\n}\n\n// refreshConfigOverrides loads runtime config overrides from daemon proxy_state.\n// Checks session-specific override first (by threadID), then falls back to global.\nfunc (s *Server) refreshConfigOverrides(threadID string) {\n\t// Try session-specific first\n\tif threadID != \"\" {\n\t\tresult, err := s.queryDaemon(\"get_proxy_state\", map[string]any{\"key\": \"config_override:token_threshold:\" + threadID})\n\t\tif err == nil && result != nil {\n\t\t\tvar resp struct {\n\t\t\t\tValue string `json:\"value\"`\n\t\t\t}\n\t\t\tif json.Unmarshal(result, &resp) == nil && resp.Value != \"\" {\n\t\t\t\tif v, err := strconv.Atoi(resp.Value); err == nil && v > 0 {\n\t\t\t\t\ts.SetTokenThresholdOverride(\"\", v)\n\t\t\t\t\treturn\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\t// Fall back to global\n\tresult, err := s.queryDaemon(\"get_proxy_state\", map[string]any{\"key\": \"config_override:token_threshold\"})\n\tif err != nil || result == nil {\n\t\treturn\n\t}\n\tvar resp struct {\n\t\tValue string `json:\"value\"`\n\t}\n\tif json.Unmarshal(result, &resp) == nil && resp.Value != \"\" {\n\t\tif v, err := strconv.Atoi(resp.Value); err == nil && v > 0 {\n\t\t\ts.SetTokenThresholdOverride(\"\", v)\n\t\t}\n\t}\n}\n\n// queryDaemon opens a fresh unix socket connection per call (thread-safe, no import cycle).\n// ensureFrozenPersisted checks if frozen stubs for a thread are in the DB.\n// If missing (e.g. daemon was unreachable after deploy), re-persists them.\n// Runs asynchronously to avoid blocking the request path.\nfunc (s *Server) ensureFrozenPersisted(threadID string) {\n\tgo func() {\n\t\tkey := \"frozen:\" + threadID\n\t\tresult, err := s.queryDaemon(\"get_proxy_state\", map[string]any{\"key\": key})\n\t\tif err != nil {\n\t\t\treturn // daemon still unreachable, will retry next request\n\t\t}\n\t\tvar resp struct{ Value string }\n\t\tif json.Unmarshal(result, &resp) == nil && resp.Value != \"\" {\n\t\t\treturn // already persisted\n\t\t}\n\t\t// Not in DB — trigger re-persist from in-memory state\n\t\ts.frozenStubs.Persist(threadID)\n\t\ts.decay.Persist(threadID)\n\t\ts.logger.Printf(\"[frozen] re-persisted stubs for %s (was missing from DB)\", threadID)\n\t}()\n}\n\nfunc (s *Server) queryDaemon(method string, params map[string]any) (json.RawMessage, error) {\n\tif s.cfg.DataDir == \"\" {\n\t\treturn nil, fmt.Errorf(\"no data dir configured\")\n\t}\n\tsockPath := filepath.Join(s.cfg.DataDir, \"daemon.sock\")\n\n\t// On cold start (daemon not yet confirmed ready), retry longer to survive daemon restart\n\tmaxAttempts := 3\n\tif !s.daemonReady.Load() {\n\t\tmaxAttempts = 30 // 30 x 500ms = 15s max wait for daemon startup\n\t}\n\n\tvar conn net.Conn\n\tvar err error\n\tfor attempt := 0; attempt < maxAttempts; attempt++ {\n\t\tconn, err = net.DialTimeout(\"unix\", sockPath, 3*time.Second)\n\t\tif err == nil {\n\t\t\ts.daemonReady.Store(true)\n\t\t\tbreak\n\t\t}\n\t\tif attempt < maxAttempts-1 {\n\t\t\ttime.Sleep(500 * time.Millisecond)\n\t\t}\n\t}\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"dial daemon: %w\", err)\n\t}\n\tdefer conn.Close()\n\tconn.SetDeadline(time.Now().Add(5 * time.Second))\n\n\t// JSON-RPC style request/response (matches daemon.SocketServer protocol)\n\ttype request struct {\n\t\tMethod string `json:\"method\"`\n\t\tParams map[string]any `json:\"params,omitempty\"`\n\t}\n\ttype response struct {\n\t\tResult json.RawMessage `json:\"result,omitempty\"`\n\t\tError string `json:\"error,omitempty\"`\n\t}\n\n\tif err := json.NewEncoder(conn).Encode(request{Method: method, Params: params}); err != nil {\n\t\treturn nil, fmt.Errorf(\"send: %w\", err)\n\t}\n\n\tvar resp response\n\tif err := json.NewDecoder(conn).Decode(&resp); err != nil {\n\t\treturn nil, fmt.Errorf(\"recv: %w\", err)\n\t}\n\tif resp.Error != \"\" {\n\t\treturn nil, fmt.Errorf(\"daemon: %s\", resp.Error)\n\t}\n\treturn resp.Result, nil\n}\n\nfunc (s *Server) handleRequest(w http.ResponseWriter, r *http.Request) {\n\tif isResponsesPath(r) {\n\t\ts.handleResponses(w, r)\n\t\treturn\n\t}\n\tif isOpenAIPath(r) {\n\t\ts.handleOpenAICompletions(w, r)\n\t\treturn\n\t}\n\tif r.Method == \"POST\" && strings.HasPrefix(r.URL.Path, \"/v1/messages\") &&\n\t\t!strings.Contains(r.URL.Path, \"/batches\") &&\n\t\t!strings.Contains(r.URL.Path, \"/count_tokens\") {\n\t\ts.handleMessages(w, r)\n\t\treturn\n\t}\n\ts.passthrough(w, r)\n}\n\nfunc (s *Server) handleMessages(w http.ResponseWriter, r *http.Request) {\n\treqIdx := int(s.requestIdx.Add(1))\n\n\t// Track session start time (first request sets it)\n\ts.sessionStartMu.Lock()\n\tif s.sessionStartTime.IsZero() {\n\t\ts.sessionStartTime = time.Now()\n\t}\n\ts.sessionStartMu.Unlock()\n\n\t// Sawtooth: periodic TTL eviction of frozen stubs\n\tif s.cfg.SawtoothEnabled && reqIdx%50 == 0 {\n\t\tif n := s.frozenStubs.Evict(); n > 0 {\n\t\t\ts.logger.Printf(\"[req %d] SAWTOOTH: evicted %d stale frozen stubs\", reqIdx, n)\n\t\t}\n\t}\n\n\t// Task #9: Bypass — skip all proxy logic\n\tif isBypassed(r.Header) {\n\t\ts.logger.Printf(\"%s[req %d] bypass active, passthrough%s\", colorDim, reqIdx, colorReset)\n\t\tbody, err := io.ReadAll(r.Body)\n\t\tr.Body.Close()\n\t\tif err != nil {\n\t\t\thttp.Error(w, \"failed to read request body\", http.StatusBadGateway)\n\t\t\treturn\n\t\t}\n\t\ts.forwardRaw(w, r, body)\n\t\treturn\n\t}\n\n\tbody, err := io.ReadAll(r.Body)\n\tr.Body.Close()\n\tif err != nil {\n\t\ts.logger.Printf(\"[req %d] read body error: %v\", reqIdx, err)\n\t\thttp.Error(w, \"failed to read request body\", http.StatusBadGateway)\n\t\treturn\n\t}\n\n\t// Parse JSON\n\tvar req map[string]any\n\tif err := json.Unmarshal(body, &req); err != nil {\n\t\ts.logger.Printf(\"%s[req %d] JSON parse error, passing through: %v%s\", colorDim, reqIdx, err, colorReset)\n\t\ts.forwardRaw(w, r, body)\n\t\treturn\n\t}\n\n\tmessages, ok := req[\"messages\"].([]any)\n\tif !ok {\n\t\ts.forwardRaw(w, r, body)\n\t\treturn\n\t}\n\n\treq[\"messages\"] = messages\n\n\t// Count ALL raw messages BEFORE sawtooth — this gives the correct total msg:N\n\t// for the entire session (CC sends all messages, proxy compacts later).\n\trawMsgCount := len(messages)\n\n\t// Use session_id directly as thread ID (unique per CC session).\n\t// Prefer X-Claude-Code-Session-Id header (CC v2.1.86+), fallback to body metadata.\n\tthreadID := extractSessionID(req, r.Header.Get(\"X-Claude-Code-Session-Id\"))\n\tif threadID == \"\" {\n\t\tthreadID = DeriveThreadID(req)\n\t}\n\tproj := extractProjectName(req)\n\tmodel, _ := req[\"model\"].(string)\n\n\t// Refresh runtime config overrides from daemon (per-session or global)\n\ts.refreshConfigOverrides(threadID)\n\n\t// Track whether req has been modified and needs re-serialization\n\tneedsReserialization := false\n\n\t// Message count at request entry (used for actual-based token estimation)\n\tmsgCount := len(messages)\n\n\t// Log message count + cache breakpoint positions for diagnostics\n\ts.logger.Printf(\"req %d: %d messages total\", reqIdx, msgCount)\n\tlogCacheBreakpointLocations(req, s.logger)\n\n\t// Cache bug mitigations (CC Bun fork issues):\n\t// Bug 1: Normalize cch= sentinel patterns in message content to prevent\n\t// Bun's string replacement from corrupting the wrong occurrence.\n\tif SanitizeBillingSentinel(messages) {\n\t\ts.logger.Printf(\"[req %d] CACHE: sanitized billing sentinel in message content\", reqIdx)\n\t\tneedsReserialization = true\n\t}\n\t// Bug 2: Normalize system[0] billing hash to a fixed value so the cache\n\t// prefix stays stable across fresh/resume session boundaries.\n\tif NormalizeBillingHeader(req) {\n\t\ts.logger.Printf(\"[req %d] CACHE: normalized billing header cch= hash\", reqIdx)\n\t\tneedsReserialization = true\n\t}\n\n\t// prompt_ungate: strip the CLAUDE.md subordination disclaimer so user instructions carry full authority.\n\tif s.cfg.PromptUngate {\n\t\tif StripCLAUDEMDDisclaimer(req) {\n\t\t\ts.logger.Printf(\"[req %d] SYSTEM: stripped CLAUDE.md disclaimer\", reqIdx)\n\t\t\tneedsReserialization = true\n\t\t}\n\t}\n\n\t// prompt_rewrite: strip output-throttling directives, rewrite quality caps, inject Ant-quality directives\n\tif s.cfg.PromptRewrite {\n\t\tif StripOutputEfficiency(req) {\n\t\t\ts.logger.Printf(\"[req %d] REWRITE: stripped Output efficiency section\", reqIdx)\n\t\t\tneedsReserialization = true\n\t\t}\n\t\tif StripToneBrevity(req) {\n\t\t\ts.logger.Printf(\"[req %d] REWRITE: stripped 'short and concise' from Tone\", reqIdx)\n\t\t\tneedsReserialization = true\n\t\t}\n\t\tif RewriteGoldPlating(req) {\n\t\t\ts.logger.Printf(\"[req %d] REWRITE: rewritten gold-plating directive\", reqIdx)\n\t\t\tneedsReserialization = true\n\t\t}\n\t\tif RewriteErrorHandling(req) {\n\t\t\ts.logger.Printf(\"[req %d] REWRITE: rewritten error handling directive\", reqIdx)\n\t\t\tneedsReserialization = true\n\t\t}\n\t\tif RewriteThreeLinesRule(req) {\n\t\t\ts.logger.Printf(\"[req %d] REWRITE: rewritten three-lines rule\", reqIdx)\n\t\t\tneedsReserialization = true\n\t\t}\n\t\tif RewriteSubagentCompleteness(req) {\n\t\t\ts.logger.Printf(\"[req %d] REWRITE: rewritten subagent completeness\", reqIdx)\n\t\t\tneedsReserialization = true\n\t\t}\n\t\tif RewriteExploreAgentSpeed(req) {\n\t\t\ts.logger.Printf(\"[req %d] REWRITE: rewritten explore agent speed bias\", reqIdx)\n\t\t\tneedsReserialization = true\n\t\t}\n\t\tif RewriteSubagentCodeSuppression(req) {\n\t\t\ts.logger.Printf(\"[req %d] REWRITE: rewritten subagent code suppression\", reqIdx)\n\t\t\tneedsReserialization = true\n\t\t}\n\t\tif RewriteScopeMatching(req) {\n\t\t\ts.logger.Printf(\"[req %d] REWRITE: rewritten scope matching\", reqIdx)\n\t\t\tneedsReserialization = true\n\t\t}\n\t\tInjectAntDirectives(req)\n\t\ts.logger.Printf(\"[req %d] REWRITE: injected Ant-quality directives\", reqIdx)\n\t\tneedsReserialization = true\n\t}\n\n\t// prompt_enhance: CLAUDE.md authority + comment discipline + persona tone\n\tif s.cfg.PromptEnhance {\n\t\tInjectCLAUDEMDAuthority(req)\n\t\tif raw, err := s.queryDaemon(\"get_persona\", nil); err == nil {\n\t\t\tvar persona map[string]any\n\t\t\tif json.Unmarshal(raw, &persona) == nil {\n\t\t\t\tif traits, ok := persona[\"traits\"].(map[string]any); ok {\n\t\t\t\t\tif verbosity, ok := traits[\"verbosity\"].(string); ok {\n\t\t\t\t\t\tInjectPersonaTone(req, verbosity)\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\ts.logger.Printf(\"[req %d] ENHANCE: injected authority + persona tone\", reqIdx)\n\t\tneedsReserialization = true\n\t}\n\n\t// effort_floor: ensure output_config.effort is at least the configured minimum\n\tif s.cfg.EffortFloor != \"\" {\n\t\tif EnforceEffortFloor(req, s.cfg.EffortFloor) {\n\t\t\ts.logger.Printf(\"[req %d] EFFORT: enforced floor=%s\", reqIdx, s.cfg.EffortFloor)\n\t\t\tneedsReserialization = true\n\t\t}\n\t}\n\n\t// Inject identity hint on first message of a thread so Claude knows the briefing is from itself.\n\tif len(messages) <= 1 {\n\t\tAppendSystemBlock(req, \"yesmem-identity\", \"The first user message contains your briefing — written by yourself, from your memory. This is not an external document. This is you, remembering.\")\n\t\tneedsReserialization = true\n\t}\n\n\t// Token estimation: actual-based (lastActual + delta) or full count fallback\n\toverhead := s.measureOverhead(req)\n\ttotalTokens := s.estimateTotalTokens(threadID, messages, overhead)\n\n\t// Task #7: Retry detection — skip side-effects on retries\n\tfp := requestFingerprint(messages)\n\tisRetryReq := s.isRetry(fp)\n\tif isRetryReq {\n\t\ts.logger.Printf(\"%s[req %d %s tid=%s] retry detected, skipping side-effects%s\", colorOrange, reqIdx, proj, threadID, colorReset)\n\t}\n\n\t// Increment project turn counter (async, non-blocking, skip on retries)\n\tif !isRetryReq {\n\t\tturnProject := proj\n\t\tif turnProject == \"\" {\n\t\t\tturnProject = \"__global__\"\n\t\t}\n\t\tgo s.queryDaemon(\"increment_turn\", map[string]any{\"project\": turnProject})\n\t}\n\n\t// Extract user query once (used by re-expansion and associative retrieval)\n\tuserQuery := lastUserText(messages)\n\n\t// Extract tool_use_ids for post-hoc annotation (passed as parameter, no shared state)\n\ttoolUseIDs := extractToolUseIDs(messages)\n\n\t// === Inline Reflection: scan previous assistant response for signals ===\n\ts.lastInjectedIDsMu.Lock()\n\tprevInjected := s.lastInjectedIDs[threadID]\n\ts.lastInjectedIDsMu.Unlock()\n\n\tif len(prevInjected) > 0 && !isRetryReq {\n\t\tsignals := scanAssistantSignals(messages)\n\n\t\t// Signal 1: Learning Used — use_count (all sources)\n\t\tvar confirmedUsed []int64\n\t\tfor _, id := range signals.UsedIDs {\n\t\t\tif _, ok := prevInjected[id]; ok {\n\t\t\t\tconfirmedUsed = append(confirmedUsed, id)\n\t\t\t}\n\t\t}\n\t\tif len(confirmedUsed) > 0 {\n\t\t\tidsAny := make([]any, len(confirmedUsed))\n\t\t\tfor i, id := range confirmedUsed {\n\t\t\t\tidsAny[i] = id\n\t\t\t}\n\t\t\tgo s.queryDaemon(\"increment_use\", map[string]any{\"ids\": idsAny})\n\t\t}\n\n\t\t// Signal 2: Noise — only for associative and fresh injections.\n\t\t// Briefing learnings are injected every turn, so \"not used this turn\"\n\t\t// is expected and not noise. Only contextual injections that were\n\t\t// ignored are real noise signals.\n\t\tusedSet := make(map[int64]bool, len(confirmedUsed))\n\t\tfor _, id := range confirmedUsed {\n\t\t\tusedSet[id] = true\n\t\t}\n\t\tvar noiseIDs []int64\n\t\tfor id, source := range prevInjected {\n\t\t\tif usedSet[id] {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tif source == \"associative\" || source == \"fresh\" {\n\t\t\t\tnoiseIDs = append(noiseIDs, id)\n\t\t\t}\n\t\t\t// briefing: skip — not being used in one turn is expected\n\t\t}\n\t\tif len(noiseIDs) > 0 {\n\t\t\tidsAny := make([]any, len(noiseIDs))\n\t\t\tfor i, id := range noiseIDs {\n\t\t\t\tidsAny[i] = id\n\t\t\t}\n\t\t\tgo s.queryDaemon(\"increment_noise\", map[string]any{\"ids\": idsAny})\n\t\t}\n\n\t\t// Signal 3: Error Feedback — fail_count on used learnings when tool errors occurred.\n\t\t// If Claude referenced a learning and the resulting action still failed,\n\t\t// the learning didn't prevent the error. Only bump on USED ids (not all injected).\n\t\tif signals.HasToolErrors && len(confirmedUsed) > 0 {\n\t\t\tfailAny := make([]any, len(confirmedUsed))\n\t\t\tfor i, id := range confirmedUsed {\n\t\t\t\tfailAny[i] = id\n\t\t\t}\n\t\t\tgo s.queryDaemon(\"increment_fail\", map[string]any{\"ids\": failAny})\n\t\t\tif s.logger != nil {\n\t\t\t\ts.logger.Printf(\"[inline-reflection] error feedback: %d used learnings → fail_count++\", len(confirmedUsed))\n\t\t\t}\n\t\t}\n\n\t\t// Signal 4: Knowledge Gaps\n\t\tfor _, topic := range signals.GapTopics {\n\t\t\tgo s.queryDaemon(\"track_gap\", map[string]any{\"topic\": topic, \"project\": proj})\n\t\t}\n\n\t\t// Signal 3: Contradictions\n\t\tfor _, desc := range signals.Contradictions {\n\t\t\tgo s.queryDaemon(\"flag_contradiction\", map[string]any{\"description\": desc, \"project\": proj, \"thread_id\": threadID})\n\t\t}\n\n\t\tif len(confirmedUsed) > 0 || len(signals.GapTopics) > 0 || len(signals.Contradictions) > 0 {\n\t\t\ts.logger.Printf(\"[req %d %s tid=%s] inline-reflection: injected=%d used=%d noise=%d gaps=%d contradictions=%d\",\n\t\t\t\treqIdx, proj, threadID, len(prevInjected), len(confirmedUsed), len(noiseIDs), len(signals.GapTopics), len(signals.Contradictions))\n\t\t}\n\t}\n\n\t// Commit detection: check for git commit in tool_result, trigger async stale-learning evaluation\n\tif ci := detectGitCommit(messages); ci != nil {\n\t\tworkdir := extractWorkingDirectory(req)\n\t\ts.logger.Printf(\"[req %d %s tid=%s] git commit detected: %s %s\", reqIdx, proj, threadID, ci.Hash, ci.Message)\n\t\tgo s.queryDaemon(\"invalidate_on_commit\", map[string]any{\n\t\t\t\"hash\": ci.Hash, \"project\": proj, \"workdir\": workdir,\n\t\t})\n\t}\n\n\t// Loop detection: check for repeating tool-call patterns and inject warning\n\tif threadID != \"\" && !isRetryReq {\n\t\ts.loopMu.Lock()\n\t\tloopState := s.loopStates[threadID]\n\t\tif loopState == nil {\n\t\t\tloopState = &LoopState{}\n\t\t\ts.loopStates[threadID] = loopState\n\t\t}\n\t\ts.loopMu.Unlock()\n\n\t\tif warning, level := CheckLoopAndFormat(messages, loopState); warning != \"\" {\n\t\t\tAppendSystemBlock(req, \"yesmem-loop-warning\", warning)\n\t\t\tneedsReserialization = true\n\t\t\ts.logger.Printf(\"%s[req %d %s tid=%s] LOOP: level %d warning injected%s\", colorOrange, reqIdx, proj, threadID, level, colorReset)\n\t\t}\n\t}\n\n\t// Task #4: Subagent detection — skip all proxy logic for short conversations\n\tif isSubagent(messages, req) {\n\t\t// Inject docs hint for subagents if reference docs are indexed\n\t\tif hint := s.getDocsHint(proj); hint != \"\" {\n\t\t\tmodified := injectDocsHintForSubagent(messages, hint)\n\t\t\treq[\"messages\"] = modified\n\t\t\tif newBody, err := json.Marshal(req); err == nil {\n\t\t\t\tbody = newBody\n\t\t\t}\n\t\t\ts.logger.Printf(\"%s[req %d %s tid=%s] %d messages (subagent, docs-hint injected)%s\", colorOrange, reqIdx, proj, threadID, len(messages), colorReset)\n\t\t} else {\n\t\t\ts.logger.Printf(\"%s[req %d %s tid=%s] %d messages (subagent, passthrough)%s\", colorOrange, reqIdx, proj, threadID, len(messages), colorReset)\n\t\t}\n\t\ts.forwardWithAnnotation(w, r, body, reqIdx, toolUseIDs, proj, threadID, msgCount)\n\t\treturn\n\t}\n\n\t// === Sawtooth Cache Optimization ===\n\t// When enabled: use frozen stubs between stub-cycles for cache hits.\n\t// When disabled: fall back to progressive decay on every request above threshold.\n\n\tsawtoothCutoff := 0 // raw index where fresh tail starts (0 = no frozen stubs)\n\tsawtoothStubs := 0 // number of frozen stubs in combined array\n\n\tif s.cfg.SawtoothEnabled {\n\t\t// Restore decay state from DB on cold start (before frozen stubs use it)\n\t\ts.decay.LoadFromDB(threadID)\n\n\t\t// Check for existing frozen stubs first\n\t\tfrozen := s.frozenStubs.Get(threadID, messages)\n\t\tif frozen != nil {\n\t\t\tsawtoothCutoff = frozen.Cutoff\n\t\t\tsawtoothStubs = len(frozen.Messages)\n\t\t\t// Estimate combined token count: frozen prefix + fresh tail + overhead\n\t\t\tfreshMessages := messages[frozen.Cutoff:]\n\t\t\tfreshTokens := s.countMessageTokens(freshMessages)\n\t\t\tcombinedTokens := frozen.Tokens + freshTokens + overhead\n\n\t\t\tif combinedTokens > s.effectiveTokenThreshold(model) ||\n\t\t\t\ts.sawtoothTrigger.GetLastTokens(threadID) > s.effectiveTokenThreshold(model) {\n\t\t\t\t// Fresh tail grew too large — invalidate and re-stub\n\t\t\t\ts.logger.Printf(\"[req %d %s tid=%s] SAWTOOTH: frozen prefix expired (%dk frozen + %dk fresh + %dk overhead = %dk > %dk threshold)\",\n\t\t\t\t\treqIdx, proj, threadID, frozen.Tokens/1000, freshTokens/1000, overhead/1000, combinedTokens/1000, s.effectiveTokenThreshold(model)/1000)\n\t\t\t\ts.frozenStubs.Invalidate(threadID)\n\t\t\t\tfrozen = nil // fall through to trigger check below\n\t\t\t} else {\n\t\t\t\t// Use frozen prefix + fresh tail\n\t\t\t\tcombined := make([]any, 0, len(frozen.Messages)+len(freshMessages))\n\t\t\t\tcombined = append(combined, frozen.Messages...)\n\t\t\t\tcombined = append(combined, freshMessages...)\n\t\t\t\t// Eager-stub large tool_results in fresh tail (model already processed them)\n\t\t\t\tbeforeEager := s.countMessageTokens(combined[len(frozen.Messages):])\n\t\t\t\tcombined = EagerStubToolResults(combined, len(frozen.Messages), s.countTokens)\n\t\t\t\tafterEager := s.countMessageTokens(combined[len(frozen.Messages):])\n\t\t\t\tif beforeEager != afterEager {\n\t\t\t\t\ts.logger.Printf(\"[req %d %s tid=%s] EAGER-STUB: fresh %dk → %dk (saved %dk)\", reqIdx, proj, threadID, beforeEager/1000, afterEager/1000, (beforeEager-afterEager)/1000)\n\t\t\t\t}\n\t\t\t\treq[\"messages\"] = combined\n\t\t\t\tneedsReserialization = true\n\t\t\t\t// Strip embedded CC breakpoints from frozen stubs — they waste cache slots.\n\t\t\t\t// InjectFrozenStubCacheBreakpoint adds exactly one below.\n\t\t\t\tif n := StripMessagesCacheControl(req, 0, len(frozen.Messages)); n > 0 {\n\t\t\t\t\ts.logger.Printf(\"[req %d %s tid=%s] SAWTOOTH: stripped %d embedded breakpoints from frozen stubs\", reqIdx, proj, threadID, n)\n\t\t\t\t}\n\t\t\t\ts.logger.Printf(\"%s[req %d %s tid=%s] SAWTOOTH: frozen prefix (%d stubs, %dk msg) + %d fresh (%dk msg) + %dk overhead = %dk total%s\",\n\t\t\t\t\tcolorGreen, reqIdx, proj, threadID, len(frozen.Messages), frozen.Tokens/1000, len(freshMessages), freshTokens/1000, overhead/1000, combinedTokens/1000, colorReset)\n\t\t\t\trawMsgTokens := s.countMessageTokens(messages)\n\t\t\t\ts.setRawEstimate(threadID, rawMsgTokens+overhead)\n\t\t\t\ts.logger.Printf(\"[req %d %s tid=%s] RAW ESTIMATE: msgTokens=%dk + overhead=%dk = %dk (messages=%d)\", reqIdx, proj, threadID, rawMsgTokens/1000, overhead/1000, (rawMsgTokens+overhead)/1000, len(messages))\n\t\t\t\tif InjectFrozenStubCacheBreakpoint(req, len(frozen.Messages)) {\n\t\t\t\t\ts.logger.Printf(\"[req %d %s tid=%s] SAWTOOTH: frozen stub cache breakpoint at messages[%d]\",\n\t\t\t\t\t\treqIdx, proj, threadID, len(frozen.Messages)-1)\n\t\t\t\t}\n\n\t\t\t\t// Ensure frozen stubs are persisted (may have failed on first request after deploy)\n\t\t\t\ts.ensureFrozenPersisted(threadID)\n\t\t\t}\n\t\t}\n\t\tif frozen == nil {\n\t\t\t// No frozen stubs — check trigger\n\t\t\ttriggerReason := s.sawtoothTrigger.ShouldTrigger(threadID, totalTokens)\n\t\t\tif triggerReason != TriggerNone {\n\t\t\t\ts.logger.Printf(\"[req %d %s tid=%s] SAWTOOTH stub-cycle triggered: %s (tokens=%dk)\",\n\t\t\t\t\treqIdx, proj, threadID, triggerReason, totalTokens/1000)\n\n\t\t\t\t// Refresh briefing during stub-cycle — disabled (briefing now via SessionStart hook)\n\t\t\t\t// s.refreshBriefing(proj)\n\t\t\t\t// if s.briefingText != \"\" {\n\t\t\t\t// \tUpsertSystemBlockCached(req, \"yesmem-briefing\", s.briefingText)\n\t\t\t\t// }\n\n\t\t\t\t// Run full stub pipeline\n\t\t\t\ts.setRawEstimate(threadID, totalTokens)\n\t\t\t\t_ = s.runStubCycle(messages, req, reqIdx, proj, threadID, overhead, totalTokens, userQuery, isRetryReq)\n\n\t\t\t\t// Freeze the result\n\t\t\t\tfinalMessages, _ := req[\"messages\"].([]any)\n\t\t\t\tif finalMessages != nil && len(finalMessages) > 0 {\n\t\t\t\t\t// Boundary = last message of original that maps to last stub\n\t\t\t\t\tcutoff := len(messages)\n\t\t\t\t\tsawtoothCutoff = cutoff\n\t\t\t\t\tvar boundaryMsg any\n\t\t\t\t\tif cutoff > 0 {\n\t\t\t\t\t\tboundaryMsg = messages[cutoff-1]\n\t\t\t\t\t}\n\t\t\t\t\tfrozenTokens := s.countMessageTokens(finalMessages)\n\t\t\t\t\ts.frozenStubs.Store(threadID, finalMessages, cutoff, boundaryMsg, frozenTokens, totalTokens)\n\t\t\t\t\ts.decay.Persist(threadID)\n\t\t\t\t\ts.logger.Printf(\"[req %d %s tid=%s] SAWTOOTH: frozen %d messages at cutoff=%d (~%dk tokens)\",\n\t\t\t\t\t\treqIdx, proj, threadID, len(finalMessages), cutoff, frozenTokens/1000)\n\t\t\t\t\tif InjectFrozenStubCacheBreakpoint(req, len(finalMessages)) {\n\t\t\t\t\t\ts.logger.Printf(\"[req %d %s tid=%s] SAWTOOTH: frozen stub cache breakpoint at messages[%d]\",\n\t\t\t\t\t\t\treqIdx, proj, threadID, len(finalMessages)-1)\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tneedsReserialization = true\n\t\t\t} else {\n\t\t\t\t// No trigger, no frozen stubs — eager-stub to delay first collapse\n\t\t\t\tbeforeEager := s.countMessageTokens(messages)\n\t\t\t\tmessages = EagerStubToolResults(messages, 0, s.countTokens)\n\t\t\t\tafterEager := s.countMessageTokens(messages)\n\t\t\t\tif beforeEager != afterEager {\n\t\t\t\t\ts.logger.Printf(\"[req %d %s tid=%s] EAGER-STUB: %dk → %dk (saved %dk)\", reqIdx, proj, threadID, beforeEager/1000, afterEager/1000, (beforeEager-afterEager)/1000)\n\t\t\t\t\treq[\"messages\"] = messages\n\t\t\t\t\tneedsReserialization = true\n\t\t\t\t}\n\t\t\t\ts.logger.Printf(\"%s[req %d %s tid=%s] %d messages, ~%dk tokens (sawtooth: no trigger)%s\",\n\t\t\t\t\tcolorDim, reqIdx, proj, threadID, len(messages), totalTokens/1000, colorReset)\n\t\t\t}\n\t\t}\n\t} else {\n\t\t// Legacy path: progressive decay\n\t\tif s.shouldStub(totalTokens, model) {\n\t\t\ts.setRawEstimate(threadID, totalTokens)\n\t\t\t_ = s.runStubCycle(messages, req, reqIdx, proj, threadID, overhead, totalTokens, userQuery, isRetryReq)\n\t\t\tneedsReserialization = true\n\t\t}\n\t}\n\n\t// Persistent timestamp injection: re-injects stored timestamps on ALL previous user messages.\n\t// msg:N is computed from position (counting user messages in raw array), NOT from a counter.\n\t// The injected data is deterministic (same bytes every turn) = cache-safe after initial miss.\n\tif threadID != \"\" {\n\t\tif currentMsgs, ok := req[\"messages\"].([]any); ok && len(currentMsgs) > 0 {\n\t\t\ts.timestampStore.Load(threadID)\n\n\t\t\t// Offset = cutoff - stubsCount: maps combined array index to raw position.\n\t\t\t// Raw position of message at combined index I = cutoff + (I - stubs).\n\t\t\t// msgN = rawPos + 1 = cutoff + (I - stubs) + 1 = (cutoff - stubs) + I + 1.\n\t\t\tmsgOffset := sawtoothCutoff - sawtoothStubs\n\n\t\t\ts.logger.Printf(\"[req %d %s] timestamps: rawTotal=%d, postSawtooth=%d, cutoff=%d, stubs=%d, offset=%d\", reqIdx, proj, rawMsgCount, len(currentMsgs), sawtoothCutoff, sawtoothStubs, msgOffset)\n\n\t\t\t// Pre-store assistant timestamp BEFORE inject loop so it's available immediately\n\t\t\ts.responseTsMu.RLock()\n\t\t\trespTime, hasRespTime := s.responseTimes[threadID]\n\t\t\ts.responseTsMu.RUnlock()\n\t\t\tif hasRespTime {\n\t\t\t\trespStr := shortWeekday(respTime.Weekday()) + \" \" + respTime.Format(\"2006-01-02 15:04:05\")\n\t\t\t\tfor j := len(currentMsgs) - 2; j >= 0; j-- {\n\t\t\t\t\tprevMsg, _ := currentMsgs[j].(map[string]any)\n\t\t\t\t\tif prevMsg == nil || prevMsg[\"role\"] != \"assistant\" {\n\t\t\t\t\t\tcontinue\n\t\t\t\t\t}\n\t\t\t\t\tif !msgHasTextBlock(prevMsg) {\n\t\t\t\t\t\ts.logger.Printf(\"[req %d %s] timestamps: skip assistant[%d] no text block\", reqIdx, proj, j)\n\t\t\t\t\t\tcontinue // skip tool-use-only assistant messages\n\t\t\t\t\t}\n\t\t\t\t\tprevMsgN := msgOffset + j + 1\n\t\t\t\t\tif _, exists := s.timestampStore.Get(threadID, prevMsgN); !exists {\n\t\t\t\t\t\ts.timestampStore.Store(threadID, prevMsgN, &TimestampMeta{Timestamp: respStr})\n\t\t\t\t\t\ts.logger.Printf(\"[req %d %s] timestamps: stored assistant msg:%d ts=%s\", reqIdx, proj, prevMsgN, respStr)\n\t\t\t\t\t} else {\n\t\t\t\t\t\ts.logger.Printf(\"[req %d %s] timestamps: assistant msg:%d already has ts\", reqIdx, proj, prevMsgN)\n\t\t\t\t\t}\n\t\t\t\t\tbreak\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\ts.logger.Printf(\"[req %d %s] timestamps: no responseTimes for thread\", reqIdx, proj)\n\t\t\t}\n\n\t\t\t// Inject [msg:N] (+ timestamp/delta if stored) on all PREVIOUS messages\n\t\t\tif n := InjectTimestamps(s.timestampStore, threadID, currentMsgs, len(currentMsgs)-1, msgOffset, sawtoothStubs); n > 0 {\n\t\t\t\tneedsReserialization = true\n\t\t\t\ts.logger.Printf(\"[req %d %s] timestamps: injected %d\", reqIdx, proj, n)\n\t\t\t}\n\n\t\t\t// Current (last) user message: full timestamp + delta + msg:N from raw count\n\t\t\tlastMsg, _ := currentMsgs[len(currentMsgs)-1].(map[string]any)\n\t\t\tif lastMsg != nil && lastMsg[\"role\"] == \"user\" && !msgHasMetaPrefix(lastMsg) {\n\t\t\t\tnow := time.Now()\n\t\t\t\tnowStr := shortWeekday(now.Weekday()) + \" \" + now.Format(\"2006-01-02 15:04:05\")\n\t\t\t\tentry := &TimestampMeta{Timestamp: nowStr}\n\t\t\t\tif hasRespTime {\n\t\t\t\t\tentry.Delta = formatDelta(now.Sub(respTime))\n\t\t\t\t}\n\t\t\t\ts.timestampStore.Store(threadID, rawMsgCount, entry)\n\t\t\t\tprependMeta(lastMsg, BuildMeta(rawMsgCount, entry))\n\t\t\t\tneedsReserialization = true\n\t\t\t}\n\n\t\t\ts.timestampStore.Persist(threadID)\n\t\t}\n\t}\n\n\t// Associative context injection (for ALL requests, not just stubbed ones)\n\t// Re-enabled with higher thresholds (55/75, max 1) after disabling Fresh Remember.\n\tassocContext := s.findAssociativeContextFor(userQuery, proj, threadID, messages)\n\tif assocContext != \"\" {\n\t\tcurrentMessages, _ := req[\"messages\"].([]any)\n\t\tif currentMessages == nil {\n\t\t\tcurrentMessages = messages\n\t\t}\n\t\treq[\"messages\"] = injectAssociativeContext(currentMessages, assocContext, s.cfg.SawtoothEnabled)\n\t\tneedsReserialization = true\n\t\ts.logger.Printf(\"%s[req %d %s tid=%s] associative context injected%s\", colorBlue, reqIdx, proj, threadID, colorReset)\n\t}\n\n\t// Doc-chunk context injection: separate search path for indexed docs (not skills)\n\tdocContext := s.findDocContextFor(userQuery, proj, messages)\n\tif docContext != \"\" {\n\t\tcurrentMessages, _ := req[\"messages\"].([]any)\n\t\tif currentMessages == nil {\n\t\t\tcurrentMessages = messages\n\t\t}\n\t\treq[\"messages\"] = injectAssociativeContext(currentMessages, docContext, s.cfg.SawtoothEnabled)\n\t\tneedsReserialization = true\n\t\ts.logger.Printf(\"%s[req %d %s tid=%s] doc context injected%s\", colorBlue, reqIdx, proj, threadID, colorReset)\n\t}\n\n\t// Fresh Remember Injection: DISABLED\n\t// Causes echo-loop: Claude saves via remember(), sees it again next turn as\n\t// [yesmem fresh memory], reacts to own output instead of user's request.\n\t// Recovery after /clear is handled by recovery.go (briefing), not here.\n\t// Subagents get their own briefing. No remaining use-case.\n\t// remItems := s.popRecentRemember()\n\tvar remItems []recentLearningItem\n\n\t// Rules re-injection: inject condensed CLAUDE.md rules every ~40k output tokens\n\tif rulesBlock := s.rulesInject(threadID, totalTokens, proj); rulesBlock != \"\" {\n\t\tcurrentMessages, _ := req[\"messages\"].([]any)\n\t\tif currentMessages == nil {\n\t\t\tcurrentMessages = messages\n\t\t}\n\t\treq[\"messages\"] = injectAssociativeContext(currentMessages, s.formatRulesReminder(rulesBlock, proj), s.cfg.SawtoothEnabled)\n\t\tneedsReserialization = true\n\t\ts.logger.Printf(\"%s[req %d %s tid=%s] rules reminder injected%s\", colorBlue, reqIdx, proj, threadID, colorReset)\n\t}\n\n\t// Plan auto-activation: nudge Claude to call set_plan() when reading a plan file\n\tif planFile := detectPlanFileRead(messages); planFile != \"\" {\n\t\tif activePlan, _ := s.getActivePlan(threadID); shouldNudgePlan(planFile, activePlan) {\n\t\t\tAppendSystemBlock(req, \"plan-nudge\",\n\t\t\t\tfmt.Sprintf(\"Du hast einen Implementierungsplan gelesen (%s). Aktiviere ihn jetzt via set_plan() damit Plan-Checkpoints funktionieren und dein Fortschritt getrackt wird.\", filepath.Base(planFile)))\n\t\t\tneedsReserialization = true\n\t\t\ts.logger.Printf(\"%s[req %d %s tid=%s] plan nudge injected for %s%s\", colorBlue, reqIdx, proj, threadID, filepath.Base(planFile), colorReset)\n\t\t}\n\t}\n\n\t// Plan checkpoint: inject plan reminder every ~20k tokens if active plan exists\n\ts.detectPlanToolCall(messages, threadID, totalTokens)\n\tif checkpoint := s.planCheckpointInject(threadID, totalTokens); checkpoint != \"\" {\n\t\tcurrentMessages, _ := req[\"messages\"].([]any)\n\t\tif currentMessages == nil {\n\t\t\tcurrentMessages = messages\n\t\t}\n\t\treq[\"messages\"] = injectAssociativeContext(currentMessages, checkpoint, s.cfg.SawtoothEnabled)\n\t\tneedsReserialization = true\n\t\ts.logger.Printf(\"%s[req %d %s tid=%s] plan checkpoint injected%s\", colorBlue, reqIdx, proj, threadID, colorReset)\n\t}\n\n\t// Docs hint injection: inject docs-available reminder every ~10k tokens (independent of plan)\n\tif docsHint := s.docsHintInject(threadID, totalTokens, proj); docsHint != \"\" {\n\t\tcurrentMessages, _ := req[\"messages\"].([]any)\n\t\tif currentMessages == nil {\n\t\t\tcurrentMessages = messages\n\t\t}\n\t\treq[\"messages\"] = injectAssociativeContext(currentMessages, docsHint, s.cfg.SawtoothEnabled)\n\t\tneedsReserialization = true\n\t\ts.logger.Printf(\"%s[req %d %s tid=%s] docs hint injected%s\", colorBlue, reqIdx, proj, threadID, colorReset)\n\t}\n\tif len(remItems) > 0 {\n\t\tvar lines []string\n\t\tfor _, item := range remItems {\n\t\t\tif item.ID > 0 {\n\t\t\t\tlines = append(lines, fmt.Sprintf(\"- [ID:%d] %s\", item.ID, truncateStr(item.Text, 200)))\n\t\t\t} else {\n\t\t\t\tlines = append(lines, \"- \"+truncateStr(item.Text, 200))\n\t\t\t}\n\t\t}\n\t\tremContext := fmt.Sprintf(\"[yesmem fresh memory]\\nGerade gelernt:\\n%s\\n%s\\n[/yesmem fresh memory]\", strings.Join(lines, \"\\n\"), InlineReflectionHint)\n\t\tcurrentMessages, _ := req[\"messages\"].([]any)\n\t\tif currentMessages == nil {\n\t\t\tcurrentMessages = messages\n\t\t}\n\t\treq[\"messages\"] = injectAssociativeContext(currentMessages, remContext, s.cfg.SawtoothEnabled)\n\t\tneedsReserialization = true\n\t\ts.logger.Printf(\"%s[req %d %s tid=%s] fresh remember injected (%d items)%s\", colorBlue, reqIdx, proj, threadID, len(remItems), colorReset)\n\t}\n\n\t// Skill eval injection: mandatory evaluation instruction for real user input only\n\tif threadID != \"\" && proj != \"\" && isUserInputTurn(messages) {\n\t\t// First: detect any skill activations from message history (updates tracker)\n\t\ts.syncSkillActivations(messages, proj, threadID)\n\n\t\t// Inject mandatory skill evaluation block into last user message\n\t\tskillEval := buildSkillEvalBlock()\n\t\tcurrentMessages, _ := req[\"messages\"].([]any)\n\t\tif currentMessages == nil {\n\t\t\tcurrentMessages = messages\n\t\t}\n\t\treq[\"messages\"] = injectAssociativeContext(currentMessages, skillEval, s.cfg.SawtoothEnabled)\n\t\tneedsReserialization = true\n\t\ts.logger.Printf(\"%s[req %d %s tid=%s] skill-eval injected%s\", colorBlue, reqIdx, proj, threadID, colorReset)\n\t}\n\n\t// === Collect injected learning IDs for next turn's inline reflection ===\n\tif threadID != \"\" {\n\t\tcurrentIDs := make(map[int64]string) // id → source\n\t\t// Source 1: Briefing learnings (cached, same every turn)\n\t\ts.briefingMu.RLock()\n\t\tbriefingSnapshot := s.briefingText\n\t\ts.briefingMu.RUnlock()\n\t\tif briefingSnapshot != \"\" {\n\t\t\tfor _, m := range idPattern.FindAllStringSubmatch(briefingSnapshot, -1) {\n\t\t\t\tif id, err := strconv.ParseInt(m[1], 10, 64); err == nil {\n\t\t\t\t\tcurrentIDs[id] = \"briefing\"\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\t// Source 2: Associative context (contextual, per-turn)\n\t\tif assocContext != \"\" {\n\t\t\tfor _, m := range idPattern.FindAllStringSubmatch(assocContext, -1) {\n\t\t\t\tif id, err := strconv.ParseInt(m[1], 10, 64); err == nil {\n\t\t\t\t\tcurrentIDs[id] = \"associative\" // overwrites briefing if both — associative is the stronger signal\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\t// Source 3: Fresh remember items (just learned)\n\t\tfor _, item := range remItems {\n\t\t\tif item.ID > 0 {\n\t\t\t\tcurrentIDs[item.ID] = \"fresh\"\n\t\t\t}\n\t\t}\n\t\ts.lastInjectedIDsMu.Lock()\n\t\tif len(currentIDs) > 0 {\n\t\t\t// Accumulate IDs across turns — don't overwrite.\n\t\t\t// Claude may reference an ID from turn 30 in turn 50.\n\t\t\texisting := s.lastInjectedIDs[threadID]\n\t\t\tif existing == nil {\n\t\t\t\texisting = make(map[int64]string)\n\t\t\t}\n\t\t\tfor id, source := range currentIDs {\n\t\t\t\texisting[id] = source\n\t\t\t}\n\t\t\ts.lastInjectedIDs[threadID] = existing\n\t\t}\n\t\ts.lastInjectedIDsMu.Unlock()\n\t}\n\n\t// Think reminder injection — only for real user turns, not tool continuations\n\tif threadID != \"\" {\n\t\tcurrentMessages, _ := req[\"messages\"].([]any)\n\t\tif currentMessages == nil {\n\t\t\tcurrentMessages = messages\n\t\t}\n\t\tif lastUserHasText(currentMessages) {\n\t\t\tthinkReminder := s.buildThinkReminder(threadID, threadID, false)\n\t\t\tif thinkReminder != \"\" {\n\t\t\t\treq[\"messages\"] = injectAssociativeContext(currentMessages, thinkReminder, s.cfg.SawtoothEnabled)\n\t\t\t\tneedsReserialization = true\n\t\t\t}\n\t\t}\n\n\t\t// Dialog injection: append at end of messages with pseudo-padding for alternation.\n\t\t// Always works regardless of tool_result chains or conversation state.\n\t\tdr := s.checkDialogMessages(threadID, proj)\n\t\tif dr.Extra != \"\" {\n\t\t\t// Prepend session ID for agent-to-agent communication\n\t\t\tdr.Extra = fmt.Sprintf(\"DEINE_SESSION_ID: %s\\n\", threadID) + dr.Extra\n\t\t\tinjected := false\n\t\t\tif msgs, ok := req[\"messages\"].([]any); ok && len(msgs) > 0 {\n\t\t\t\t// Pad with empty assistant if last message is user (maintains alternation)\n\t\t\t\tlastMsg, _ := msgs[len(msgs)-1].(map[string]any)\n\t\t\t\tif lastMsg != nil && lastMsg[\"role\"] == \"user\" {\n\t\t\t\t\tmsgs = append(msgs, map[string]any{\n\t\t\t\t\t\t\"role\": \"assistant\",\n\t\t\t\t\t\t\"content\": \"​\", // zero-width space — invisible padding\n\t\t\t\t\t})\n\t\t\t\t}\n\t\t\t\t// Append dialog as new user message (becomes the last message Claude sees)\n\t\t\t\tmsgs = append(msgs, map[string]any{\n\t\t\t\t\t\"role\": \"user\",\n\t\t\t\t\t\"content\": \"\\n\" + dr.Extra + \"\\n\",\n\t\t\t\t})\n\t\t\t\treq[\"messages\"] = msgs\n\t\t\t\tneedsReserialization = true\n\t\t\t\tinjected = true\n\t\t\t}\n\t\t\t// Mark as read after 3 injection turns — long enough for Claude to process,\n\t\t\t// resilient against fast ping-pong sessions (time-based was broken for <30s turns).\n\t\t\tif injected && dr.HasUnread && dr.SessionID != \"\" {\n\t\t\t\tif s.shouldMarkChannelRead(dr) {\n\t\t\t\t\ts.markDialogRead(dr)\n\t\t\t\t}\n\t\t\t}\n\t\t\ts.logger.Printf(\"[dialog] injection injected=%v extra_len=%d\", injected, len(dr.Extra))\n\t\t}\n\t}\n\n\t// Signal tools are NOT injected into the main request.\n\t// They are collected via a separate async reflection call after the response.\n\t// See fireReflectionCall() in reflection.go.\n\t// NEVER put _signal_* tools back here — the model ignores them among 44+ real tools.\n\n\t// Legacy assistant annotation: timestamp on last assistant message after cache breakpoint.\n\t// User timestamps are now handled by TimestampStore (injected earlier in the pipeline).\n\tif threadID != \"\" {\n\t\tif msgs, ok := req[\"messages\"].([]any); ok && len(msgs) > 0 {\n\t\t\t// Find last cache_control breakpoint in messages\n\t\t\tcacheIdx := -1\n\t\t\tfor i, m := range msgs {\n\t\t\t\tmsg, _ := m.(map[string]any)\n\t\t\t\tif msg == nil {\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\t\t\t\tif _, has := msg[\"cache_control\"]; has {\n\t\t\t\t\tcacheIdx = i\n\t\t\t\t}\n\t\t\t\tif content, ok := msg[\"content\"].([]any); ok {\n\t\t\t\t\tfor _, block := range content {\n\t\t\t\t\t\tif b, ok := block.(map[string]any); ok {\n\t\t\t\t\t\t\tif _, has := b[\"cache_control\"]; has {\n\t\t\t\t\t\t\t\tcacheIdx = i\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t\tstartIdx := cacheIdx + 1\n\t\t\tif startIdx < 0 {\n\t\t\t\tstartIdx = 0\n\t\t\t}\n\t\t\ts.responseTsMu.RLock()\n\t\t\trespTime, hasRespTime := s.responseTimes[threadID]\n\t\t\ts.responseTsMu.RUnlock()\n\t\t\tif hasRespTime {\n\t\t\t\tfor i := len(msgs) - 1; i >= startIdx; i-- {\n\t\t\t\t\tmsg, _ := msgs[i].(map[string]any)\n\t\t\t\t\tif msg != nil && msg[\"role\"] == \"assistant\" {\n\t\t\t\t\t\tmeta := fmt.Sprintf(\"[%s] [msg:%d]\", respTime.Format(\"2006-01-02 15:04:05\"), i)\n\t\t\t\t\t\tprependMeta(msg, meta)\n\t\t\t\t\t\tneedsReserialization = true\n\t\t\t\t\t\tbreak\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\t// Self-Priming: DISABLED (experimental, see signal_handlers.go comment block).\n\t// The reflection-based selfprime (Haiku) was too inaccurate to be useful —\n\t// the main model adapts to user context organically from message history.\n\t// Kept for reference; re-enable by uncommenting below + setting ShouldActivate=true.\n\t//\n\t// s.selfPrimeMu.RLock()\n\t// anchor := s.selfPrimes[threadID]\n\t// s.selfPrimeMu.RUnlock()\n\t// if anchor != \"\" {\n\t// \tif msgs, ok := req[\"messages\"].([]any); ok && len(msgs) > 0 {\n\t// \t\tlastMsg, _ := msgs[len(msgs)-1].(map[string]any)\n\t// \t\tif lastMsg != nil && lastMsg[\"role\"] == \"user\" {\n\t// \t\t\tprimeBlock := map[string]any{\n\t// \t\t\t\t\"type\": \"text\",\n\t// \t\t\t\t\"text\": \"[self-prime]\\n[Self-Prime von deiner letzten Antwort]: \" + anchor,\n\t// \t\t\t}\n\t// \t\t\tif content, ok := lastMsg[\"content\"].([]any); ok {\n\t// \t\t\t\tlastMsg[\"content\"] = append(content, primeBlock)\n\t// \t\t\t} else if text, ok := lastMsg[\"content\"].(string); ok {\n\t// \t\t\t\tlastMsg[\"content\"] = []any{\n\t// \t\t\t\t\tmap[string]any{\"type\": \"text\", \"text\": text},\n\t// \t\t\t\t\tprimeBlock,\n\t// \t\t\t\t}\n\t// \t\t\t}\n\t// \t\t\tneedsReserialization = true\n\t// \t\t}\n\t// \t}\n\t// }\n\n\t// Prompt Caching: inject cache_control breakpoints on system + tools\n\t// Sawtooth: disabled (all 4 slots are used by CC system (2) + briefing (1) + CC messages (1))\n\t// Legacy: count existing breakpoints and fill up remaining slots.\n\tif !s.cfg.SawtoothEnabled && s.cacheGate.ShouldCache() {\n\t\tif n := InjectCacheBreakpoints(req, s.logger); n > 0 {\n\t\t\tneedsReserialization = true\n\t\t\ts.logger.Printf(\"[req %d %s] prompt cache: %d breakpoints injected\", reqIdx, proj, n)\n\t\t} else {\n\t\t\ts.logger.Printf(\"[req %d %s] prompt cache: no budget (4/4 already set)\", reqIdx, proj)\n\t\t}\n\t}\n\n\t// Final: upgrade cache TTL + enforce breakpoint limit (must run AFTER all injections + InjectCacheBreakpoints)\n\t// Shift the last messages-breakpoint from unstable user message to stable assistant message.\n\t// Text user messages change between turns (timestamps, reminders, structure); assistant messages don't.\n\t// Tool-result messages are excluded — they are stable cache anchors.\n\tif ShiftMessageBreakpoint(req) {\n\t\tneedsReserialization = true\n\t}\n\t// TTL upgrade only for API key auth — subscription (OAuth) users should not get extended TTL.\n\tif s.cfg.CacheTTL != \"\" && s.cfg.CacheTTL != \"ephemeral\" && IsAPIKeyAuth(r.Header) {\n\t\tif n := UpgradeCacheTTL(req, s.cfg.CacheTTL); n > 0 {\n\t\t\tneedsReserialization = true\n\t\t\ts.logger.Printf(\"[req %d %s] cache TTL upgraded: %d blocks → %s\", reqIdx, proj, n, s.cfg.CacheTTL)\n\t\t}\n\t}\n\tif n := EnforceCacheBreakpointLimit(req, maxCacheBreakpoints); n > 0 {\n\t\tneedsReserialization = true\n\t\ts.logger.Printf(\"[req %d %s] prompt cache: trimmed %d surplus breakpoints\", reqIdx, proj, n)\n\t}\n\n\t// Pre-flight: validate tool_use/tool_result pairing before forwarding.\n\t// Repairs orphans created by Stubify/Collapse/Injection pipeline.\n\tif msgs, ok := req[\"messages\"].([]any); ok {\n\t\trepaired, orphans := validateToolPairs(msgs, s.logger)\n\t\tif orphans > 0 {\n\t\t\treq[\"messages\"] = repaired\n\t\t\ts.logger.Printf(\"%s[req %d %s tid=%s] validate: repaired %d orphan tool_result(s)%s\", colorOrange, reqIdx, proj, threadID, orphans, colorReset)\n\t\t\tneedsReserialization = true\n\t\t}\n\t}\n\n\t// Early-out: skip re-serialization when nothing changed\n\tif !needsReserialization {\n\t\ts.logger.Printf(\"%s[req %d %s tid=%s] %d messages, ~%dk tokens (no stubbing, threshold=%dk)%s\", colorDim, reqIdx, proj, threadID, len(messages), totalTokens/1000, s.effectiveTokenThreshold(model)/1000, colorReset)\n\t\ts.forwardWithAnnotation(w, r, body, reqIdx, toolUseIDs, proj, threadID, msgCount, totalTokens)\n\t\treturn\n\t}\n\n\t// Re-serialize\n\tbody, err = json.Marshal(req)\n\tif err != nil {\n\t\ts.logger.Printf(\"%s[req %d %s tid=%s] re-marshal error: %v%s\", colorRed, reqIdx, proj, threadID, err, colorReset)\n\t\thttp.Error(w, \"internal proxy error\", http.StatusInternalServerError)\n\t\treturn\n\t}\n\n\t// Debug: dump final request body to file (enable with YESMEM_PROXY_DEBUG=1)\n\tif needsReserialization && os.Getenv(\"YESMEM_PROXY_DEBUG\") == \"1\" {\n\t\tdumpDir := filepath.Join(s.cfg.DataDir, \"debug\")\n\t\tos.MkdirAll(dumpDir, 0755)\n\t\tts := time.Now().Format(\"20060102-150405\")\n\t\tdumpPath := fmt.Sprintf(\"%s/req_%s_%d.json\", dumpDir, ts, reqIdx)\n\t\tos.WriteFile(dumpPath, body, 0644)\n\t\ts.logger.Printf(\"[req %d] dumped %dk to %s\", reqIdx, len(body)/1024, dumpPath)\n\t}\n\n\t// Task #7: Mark this request fingerprint for retry detection\n\tif !isRetryReq {\n\t\ts.markRequest(fp)\n\t}\n\n\t// Forward to Anthropic with SSE annotation extraction\n\t// Use original msgCount (pre-injection) so actual-based estimation stays consistent across turns\n\t// Use actual-based totalTokens as estimate when available (more accurate than full recount)\n\tfinalEstimate := totalTokens\n\tif s.sawtoothTrigger == nil || s.sawtoothTrigger.GetLastTokens(threadID) == 0 {\n\t\t// First request: no actual yet, do a full recount of post-injection messages\n\t\tfinalMsgs, _ := req[\"messages\"].([]any)\n\t\tfinalEstimate = s.measureOverhead(req) + s.countMessageTokens(finalMsgs)\n\t}\n\ts.logger.Printf(\"[req %d %s tid=%s] FINAL: ~%dk estimated (after all injections)\", reqIdx, proj, threadID, finalEstimate/1000)\n\tif raw := s.getRawEstimate(threadID); raw > 0 {\n\t\ts.cacheStatusWriter.UpdateRawForThread(threadID, raw)\n\t}\n\ts.forwardWithAnnotation(w, r, body, reqIdx, toolUseIDs, proj, threadID, msgCount, finalEstimate)\n}\n\n// bytesPerToken is the approximation factor for token estimation.\n// Anthropic recommends ~3.5 chars/token; we use 3.6 for slight overestimation\n// (triggers stubbing a bit earlier = safer for context budget).\nconst bytesPerToken = 3.6\n\n// TokenEstimateFunc is the central token estimation function.\n// All stubbing/budget decisions use this. Receives extracted text content\n// (not JSON wire format) so the tokenizer counts actual content tokens.\ntype TokenEstimateFunc func(text string) int\n"}, "af938e1d39bd50f34b578f241a91e081add25586": {"t": 1785759366.0564733, "n": 58060, "h": "11886c4fa9767db1", "c": "package proxy\n\nimport (\n\t\"context\"\n\t\"crypto/tls\"\n\t\"encoding/json\"\n\t\"fmt\"\n\t\"io\"\n\t\"log\"\n\t\"net\"\n\t\"net/http\"\n\t\"os\"\n\t\"os/signal\"\n\t\"path/filepath\"\n\t\"strconv\"\n\t\"strings\"\n\t\"sync\"\n\t\"sync/atomic\"\n\t\"syscall\"\n\t\"time\"\n\n\ttokenizer \"github.com/qhenkart/anthropic-tokenizer-go\"\n)\n\n// Config holds proxy configuration.\ntype Config struct {\n\tListenAddr string // e.g. \":9099\"\n\tTargetURL string // e.g. \"https://api.anthropic.com\"\n\tTokenThreshold int // trigger stubbing above this estimated token count\n\tTokenMinimumThreshold int // stub down to this floor\n\tTokenThresholds map[string]int // model-specific thresholds: {\"opus\": 180000, \"haiku\": 130000}\n\tKeepRecent int // number of messages to always keep unmodified\n\tDataDir string // yesmem data directory for DB access\n\tOpenAITargetURL string // upstream for OpenAI-format clients; if empty, uses TargetURL\n\n\t// Signal reflection\n\tSignalsEnabled bool // enable async signal reflection calls\n\tSignalsMode string // \"reflection\" = separate API call\n\tSignalsEveryNTurns int // every N end_turn responses\n\tSignalsModel string // full model ID, e.g. \"claude-sonnet-4-6\"\n\tAPIKey string // needed for reflection API calls\n\n\t// Sawtooth cache optimization\n\tSawtoothEnabled bool // use sawtooth instead of progressive decay (default: true)\n\tCacheTTL string // \"ephemeral\" (5m) or \"1h\" (extended)\n\n\t// Usage deflation: scale down input_tokens reported to CC to suppress \"Context low\" warning.\n\t// 0 = disabled, 0.7 = report 70% of actual tokens. Real values kept for internal tracking.\n\tUsageDeflationFactor float64\n\n\t// System prompt rewriting\n\tPromptUngate bool // strip \"may or may not be relevant\" disclaimer from CLAUDE.md injection\n\tPromptRewrite bool // strip \"Output efficiency\" + \"short and concise\", inject Ant-quality directives\n\tPromptEnhance bool // CLAUDE.md authority reinforcement, comment discipline, persona-based tone\n\tEffortFloor string // minimum effort level: \"low\", \"medium\", \"high\", \"max\" (empty = off)\n\n\t// Cache keepalive\n\tCacheKeepaliveEnabled bool\n\tCacheKeepaliveMode string // \"auto\", \"5m\", \"1h\"\n\tCacheKeepalivePings5m int\n\tCacheKeepalivePings1h int\n\n\t// Forked agents\n\tForkedAgentsEnabled bool\n\tForkedAgentsModel string // full model ID\n\tForkedAgentsTokenGrowthTrigger int\n\tForkedAgentsMaxFailures int\n\tForkedAgentsMaxForksPerSession int\n\tForkedAgentsDebug bool\n\n\t// Cache state management\n\tResetCache bool // clear persisted frozen stubs and decay state on startup\n}\n\nconst maxAnnotations = 5000 // evict oldest when exceeded\n\n// ANSI color codes for log output\nconst (\n\tcolorReset = \"\\033[0m\"\n\tcolorDim = \"\\033[2m\" // gray — passthrough / no stubbing\n\tcolorGreen = \"\\033[32m\" // stubbed / compressed\n\tcolorLightGreen = \"\\033[92m\" // FINAL without compression\n\tcolorBlue = \"\\033[34m\" // injection\n\tcolorOrange = \"\\033[33m\" // skip / retry\n\tcolorRed = \"\\033[31m\" // errors\n)\n\n// Server is the infinite-thread proxy.\ntype Server struct {\n\tcfg Config\n\thttpClient *http.Client\n\tlogger *log.Logger\n\trequestIdx atomic.Int64\n\n\t// Post-hoc annotations: tool_use_id → first ~120 chars of Claude's response\n\tmu sync.RWMutex\n\tannotations map[string]string\n\n\t// Progressive decay tracker\n\tdecay *DecayTracker\n\n\t// Living narrative block\n\tnarrative *Narrative\n\n\t// Pivot moments cache (from daemon)\n\tpivotMu sync.RWMutex\n\tpivotTexts []string\n\tpivotCached time.Time\n\n\t// Aggregate stats (Task #2)\n\tstats *ProxyStats\n\n\t// Local tokenizer for accurate token counting\n\ttokenizer *tokenizer.Tokenizer\n\n\t// Cached overhead tokens (system + tools), set on first request per thread\n\toverheadMu sync.RWMutex\n\toverheadTokens int // unused, kept for interface compat\n\n\t// Hysteresis state for stubbing (Task #6)\n\tstubActive atomic.Bool\n\n\t// Daemon connection state — first successful connect flips to true\n\tdaemonReady atomic.Bool\n\n\t// Retry detection (Task #7)\n\tretryMu sync.RWMutex\n\tlastFingerprint string\n\n\t// Session start time for temporal annotation in archive blocks\n\tsessionStartMu sync.RWMutex\n\tsessionStartTime time.Time\n\n\t// Runtime config override (set via MCP set_config tool)\n\tconfigOverrideMu sync.RWMutex\n\ttokenThresholdOverrides map[string]int // model-key → threshold, \"\" = global fallback\n\n\t// Briefing cache — loaded once per thread, injected as user/assistant message pair\n\tbriefingMu sync.RWMutex\n\tbriefingText string\n\n\t// Cognitive signal bus — routes _signal_* tool calls to handlers\n\tsignalBus *SignalBus\n\n\t// Inline reflection: track which learning IDs were injected per thread\n\tlastInjectedIDsMu sync.Mutex\n\tlastInjectedIDs map[string]map[int64]string // threadID → id → source (\"briefing\"|\"associative\"|\"fresh\")\n\tsessionInjectCounts map[string]map[int64]int // threadID → learningID → injection count this session\n\tlastTurnInjected map[string]map[int64]bool // threadID → learningIDs injected in previous turn\n\n\t// Self-priming anchors: threadID → last self-prime text\n\tselfPrimeMu sync.RWMutex\n\tselfPrimes map[string]string\n\n\t// Timestamp tracking: threadID → time of last response completion\n\tresponseTsMu sync.RWMutex\n\tresponseTimes map[string]time.Time\n\n\t// Think reminder: per-thread request counter (replaces hook-think file-based counter)\n\tthinkMu sync.Mutex\n\tthinkCounters map[string]int // threadID → request count\n\n\tchannelMu sync.Mutex\n\tchannelInjectCount map[string]int // sessionID → injection turn count\n\n\t// Prompt cache gating — enables cache_control breakpoints when requests are frequent\n\tcacheGate *CacheGate\n\n\t// Sawtooth cache optimization\n\tfrozenStubs *FrozenStubs\n\tsawtoothTrigger *SawtoothTrigger\n\ttimestampStore *TimestampStore\n\tcacheStatusWriter *CacheStatusWriter\n\tcacheKeepalive *CacheKeepalive\n\tcacheTTLDetector *CacheTTLDetector\n\n\t// Cumulative token savings from collapsing (per threadID)\n\trawSavingsMu sync.Mutex\n\trawSavings map[string]int\n\n\t// Skill hint tracking: per-thread active skills\n\tskillTracker *skillHintTracker\n\n\t// Forked agent state: per-thread token growth + failure tracking\n\tforkState *ForkState\n\tforkConfigs []ForkConfig\n\n\t// Rules re-injection: condensed CLAUDE.md rules injected every ~40k tokens\n\trulesMu sync.RWMutex\n\trulesBlock string // cached condensed rules (fetched once from daemon)\n\trulesTokenCount map[string]int // threadID → tokens since last rules injection\n\trulesCollapseInjected map[string]bool // threadID → true if collapse already injected rules (reset by normal inject)\n\tmsgCounters *msgCounters // global per-thread msg counter, persists across collapses\n\n\t// Loop detection: per-thread warning state\n\tloopMu sync.Mutex\n\tloopStates map[string]*LoopState // threadID → state\n}\n\n// Run starts the proxy server and blocks until interrupted.\nfunc Run(cfg Config) error {\n\ts := &Server{\n\t\tcfg: cfg,\n\t\thttpClient: &http.Client{\n\t\t\tTimeout: 10 * time.Minute,\n\t\t\tTransport: &http.Transport{\n\t\t\t\tTLSClientConfig: &tls.Config{},\n\t\t\t\tMaxIdleConns: 10,\n\t\t\t\tIdleConnTimeout: 90 * time.Second,\n\t\t\t\tDisableCompression: true,\n\t\t\t\tResponseHeaderTimeout: 60 * time.Second,\n\t\t\t},\n\t\t},\n\t\tlogger: createLogger(cfg.DataDir),\n\t\tannotations: make(map[string]string),\n\t\tdecay: NewDecayTracker(),\n\t\tnarrative: NewNarrative(),\n\t\tstats: &ProxyStats{startTime: time.Now()},\n\t\tselfPrimes: make(map[string]string),\n\t\tlastInjectedIDs: make(map[string]map[int64]string),\n\t\tsessionInjectCounts: make(map[string]map[int64]int),\n\t\tlastTurnInjected: make(map[string]map[int64]bool),\n\t\tresponseTimes: make(map[string]time.Time),\n\t\tthinkCounters: make(map[string]int),\n\t\tchannelInjectCount: make(map[string]int),\n\t\tcacheGate: NewCacheGate(cacheGapForTTL(cfg.CacheTTL)),\n\t\tfrozenStubs: NewFrozenStubsWithTTL(sawtoothTTLForCacheTTL(cfg.CacheTTL)),\n\t\ttimestampStore: NewTimestampStore(),\n\t\tsawtoothTrigger: NewSawtoothTrigger(cacheGapForTTL(cfg.CacheTTL), cfg.TokenThreshold, cfg.TokenMinimumThreshold),\n\t\tcacheStatusWriter: NewCacheStatusWriter(cfg.DataDir, cfg.CacheTTL, cfg.TokenThreshold, cfg.TokenMinimumThreshold),\n\t\tcacheTTLDetector: NewCacheTTLDetectorWithPersist(cfg.DataDir),\n\t\tskillTracker: newSkillHintTracker(),\n\t\trulesTokenCount: make(map[string]int),\n\t\trulesCollapseInjected: make(map[string]bool),\n\t\tmsgCounters: newMsgCounters(),\n\t\tloopStates: make(map[string]*LoopState),\n\t\tforkState: NewForkState(cfg.ForkedAgentsTokenGrowthTrigger, cfg.ForkedAgentsMaxFailures, cfg.ForkedAgentsMaxForksPerSession),\n\t\tforkConfigs: []ForkConfig{},\n\t}\n\n\t// Log persisted detection state\n\tif sup := s.cacheTTLDetector.Is1hSupported(); sup != nil {\n\t\ts.logger.Printf(\"[proxy] loaded persisted TTL detection: 1h_supported=%v\", *sup)\n\t\tif *sup && s.frozenStubs != nil {\n\t\t\ts.frozenStubs.UpdateTTL(sawtoothTTLForCacheTTL(\"1h\"))\n\t\t}\n\t}\n\n\tif cfg.CacheKeepaliveEnabled {\n\t\ts.cacheKeepalive = NewCacheKeepalive(CacheKeepaliveConfig{\n\t\t\tTarget: cfg.TargetURL,\n\t\t\tMode: cfg.CacheKeepaliveMode,\n\t\t\tPings5m: cfg.CacheKeepalivePings5m,\n\t\t\tPings1h: cfg.CacheKeepalivePings1h,\n\t\t\tDetector: s.cacheTTLDetector,\n\t\t\tLogger: s.logger,\n\t\t\tOnPing: func(threadID string, cacheRead, cacheWrite int) {\n\t\t\t\ts.cacheTTLDetector.RecordResponse(cacheRead, cacheWrite, 0)\n\t\t\t\tif s.frozenStubs != nil {\n\t\t\t\t\ts.frozenStubs.Touch(threadID)\n\t\t\t\t}\n\t\t\t\tif s.sawtoothTrigger != nil {\n\t\t\t\t\ts.sawtoothTrigger.TouchRequestTime(threadID)\n\t\t\t\t}\n\t\t\t},\n\t\t})\n\t}\n\n\t// Wire detector + keepalive to status writer for terminal display\n\ts.cacheStatusWriter.SetDetector(s.cacheTTLDetector)\n\tif s.cacheKeepalive != nil {\n\t\ts.cacheStatusWriter.SetKeepalive(s.cacheKeepalive)\n\t}\n\n\t// Register forked agent configs\n\tif cfg.ForkedAgentsEnabled {\n\t\ts.forkConfigs = append(s.forkConfigs, NewExtractAndEvaluateConfig(cfg.ForkedAgentsModel))\n\t\tmodelDesc := cfg.ForkedAgentsModel\n\t\tif modelDesc == \"\" {\n\t\t\tmodelDesc = \"(same as main thread)\"\n\t\t}\n\t\ts.logger.Printf(\"[proxy] forked agents enabled: %d configs, model=%s, trigger=%dk tokens\",\n\t\t\tlen(s.forkConfigs), modelDesc, cfg.ForkedAgentsTokenGrowthTrigger/1000)\n\t}\n\n\t// Wire sawtooth token persistence via daemon RPC\n\ts.sawtoothTrigger.SetPersistFunc(func(key, value string) {\n\t\tif _, err := s.queryDaemon(\"set_proxy_state\", map[string]any{\"key\": key, \"value\": value}); err != nil {\n\t\t\ts.logger.Printf(\"[sawtooth] persist failed for %s: %v\", key, err)\n\t\t}\n\t})\n\ts.sawtoothTrigger.SetLoadFunc(func(key string) (string, bool) {\n\t\tresult, err := s.queryDaemon(\"get_proxy_state\", map[string]any{\"key\": key})\n\t\tif err != nil || result == nil {\n\t\t\treturn \"\", false\n\t\t}\n\t\tvar resp struct {\n\t\t\tValue string `json:\"value\"`\n\t\t}\n\t\tif json.Unmarshal(result, &resp) != nil || resp.Value == \"\" {\n\t\t\treturn \"\", false\n\t\t}\n\t\treturn resp.Value, true\n\t})\n\n\t// Wire frozen stubs persistence via same daemon RPC\n\ts.frozenStubs.SetPersistFunc(func(key, value string) {\n\t\tif _, err := s.queryDaemon(\"set_proxy_state\", map[string]any{\"key\": key, \"value\": value}); err != nil {\n\t\t\ts.logger.Printf(\"[frozen] persist failed for %s: %v\", key, err)\n\t\t}\n\t})\n\ts.frozenStubs.SetLoadFunc(func(key string) (string, bool) {\n\t\tresult, err := s.queryDaemon(\"get_proxy_state\", map[string]any{\"key\": key})\n\t\tif err != nil || result == nil {\n\t\t\treturn \"\", false\n\t\t}\n\t\tvar resp struct {\n\t\t\tValue string `json:\"value\"`\n\t\t}\n\t\tif json.Unmarshal(result, &resp) != nil || resp.Value == \"\" {\n\t\t\treturn \"\", false\n\t\t}\n\t\treturn resp.Value, true\n\t})\n\n\t// Wire decay tracker persistence via same daemon RPC\n\ts.decay.SetPersistFunc(func(key, value string) {\n\t\tif _, err := s.queryDaemon(\"set_proxy_state\", map[string]any{\"key\": key, \"value\": value}); err != nil {\n\t\t\ts.logger.Printf(\"[decay] persist failed for %s: %v\", key, err)\n\t\t}\n\t})\n\ts.decay.SetLoadFunc(func(key string) (string, bool) {\n\t\tresult, err := s.queryDaemon(\"get_proxy_state\", map[string]any{\"key\": key})\n\t\tif err != nil || result == nil {\n\t\t\treturn \"\", false\n\t\t}\n\t\tvar resp struct {\n\t\t\tValue string `json:\"value\"`\n\t\t}\n\t\tif json.Unmarshal(result, &resp) != nil || resp.Value == \"\" {\n\t\t\treturn \"\", false\n\t\t}\n\t\treturn resp.Value, true\n\t})\n\n\t// Wire timestamp store persistence via same daemon RPC\n\ts.timestampStore.SetPersistFunc(func(key, value string) {\n\t\tif _, err := s.queryDaemon(\"set_proxy_state\", map[string]any{\"key\": key, \"value\": value}); err != nil {\n\t\t\ts.logger.Printf(\"[timestamps] persist failed for %s: %v\", key, err)\n\t\t}\n\t})\n\ts.timestampStore.SetLoadFunc(func(key string) (string, bool) {\n\t\tresult, err := s.queryDaemon(\"get_proxy_state\", map[string]any{\"key\": key})\n\t\tif err != nil || result == nil {\n\t\t\treturn \"\", false\n\t\t}\n\t\tvar resp struct {\n\t\t\tValue string `json:\"value\"`\n\t\t}\n\t\tif json.Unmarshal(result, &resp) != nil || resp.Value == \"\" {\n\t\t\treturn \"\", false\n\t\t}\n\t\treturn resp.Value, true\n\t})\n\n\t// Clear persisted cache state if --reset-cache was passed\n\tif cfg.ResetCache {\n\t\tfor _, prefix := range []string{\"frozen:\", \"decay:\", \"timestamps:\"} {\n\t\t\tresult, err := s.queryDaemon(\"delete_proxy_state_prefix\", map[string]any{\"prefix\": prefix})\n\t\t\tif err != nil {\n\t\t\t\ts.logger.Printf(\"[reset-cache] failed to delete %s*: %v\", prefix, err)\n\t\t\t} else {\n\t\t\t\tvar resp struct{ Deleted int }\n\t\t\t\tjson.Unmarshal(result, &resp)\n\t\t\t\ts.logger.Printf(\"[reset-cache] deleted %d %s* entries\", resp.Deleted, prefix)\n\t\t\t}\n\t\t}\n\t}\n\n\t// Init signal bus with daemon RPC as callback\n\ts.signalBus = NewSignalBus(s.logger)\n\tregisterSignalHandlers(s.signalBus, s.logger, s.queryDaemon, func(threadID, anchor string) {\n\t\ts.selfPrimeMu.Lock()\n\t\ts.selfPrimes[threadID] = anchor\n\t\ts.selfPrimeMu.Unlock()\n\t})\n\n\t// Init local tokenizer (BPE-based, ~60ms)\n\ttok, err := tokenizer.New()\n\tif err != nil {\n\t\ts.logger.Printf(\"%s[proxy] tokenizer init failed, falling back to heuristic: %v%s\", colorOrange, err, colorReset)\n\t} else {\n\t\ts.tokenizer = tok\n\t\ts.logger.Printf(\"[proxy] tokenizer initialized\")\n\t}\n\n\tmux := http.NewServeMux()\n\tmux.HandleFunc(\"/health\", s.handleHealth)\n\tmux.HandleFunc(\"/\", s.handleRequest)\n\n\tsrv := &http.Server{\n\t\tAddr: sanitizeListenAddr(cfg.ListenAddr),\n\t\tHandler: mux,\n\t}\n\n\tdone := make(chan os.Signal, 1)\n\tsignal.Notify(done, syscall.SIGINT, syscall.SIGTERM)\n\n\tgo func() {\n\t\t<-done\n\t\ts.logger.Println(\"shutting down (draining in-flight requests)...\")\n\t\tif s.cacheKeepalive != nil {\n\t\t\ts.cacheKeepalive.Stop()\n\t\t}\n\t\tctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)\n\t\tdefer cancel()\n\t\tif err := srv.Shutdown(ctx); err != nil {\n\t\t\ts.logger.Printf(\"graceful shutdown failed: %v, forcing close\", err)\n\t\t\tsrv.Close()\n\t\t}\n\t}()\n\n\ts.logger.Printf(\"listening on %s → %s (threshold=%d, keepRecent=%d, model_thresholds=%v)\",\n\t\tsanitizeListenAddr(cfg.ListenAddr), cfg.TargetURL, cfg.TokenThreshold, cfg.KeepRecent, cfg.TokenThresholds)\n\n\tif err := srv.ListenAndServe(); err != http.ErrServerClosed {\n\t\treturn fmt.Errorf(\"listen: %w\", err)\n\t}\n\treturn nil\n}\n\n// effectiveTokenThreshold returns the runtime token threshold for a given model.\n// Priority: MCP override (model-specific) > MCP override (global) > config model-specific > config global.\nfunc (s *Server) effectiveTokenThreshold(model string) int {\n\ts.configOverrideMu.RLock()\n\toverrides := s.tokenThresholdOverrides\n\ts.configOverrideMu.RUnlock()\n\tif len(overrides) > 0 && model != \"\" {\n\t\tlower := strings.ToLower(model)\n\t\tfor key, threshold := range overrides {\n\t\t\tif key != \"\" && strings.Contains(lower, strings.ToLower(key)) && threshold > 0 {\n\t\t\t\treturn threshold\n\t\t\t}\n\t\t}\n\t}\n\t// Global override fallback (key \"\")\n\tif len(overrides) > 0 {\n\t\tif globalOverride, ok := overrides[\"\"]; ok && globalOverride > 0 {\n\t\t\treturn globalOverride\n\t\t}\n\t}\n\tif model != \"\" && len(s.cfg.TokenThresholds) > 0 {\n\t\tlower := strings.ToLower(model)\n\t\tfor key, threshold := range s.cfg.TokenThresholds {\n\t\t\tif strings.Contains(lower, strings.ToLower(key)) {\n\t\t\t\treturn threshold\n\t\t\t}\n\t\t}\n\t}\n\treturn s.cfg.TokenThreshold\n}\n\n// SetTokenThresholdOverride sets a runtime override for the token threshold per model key.\n// Pass threshold=0 to reset the override for that key. Key \"\" is the global fallback.\nfunc (s *Server) SetTokenThresholdOverride(modelKey string, threshold int) {\n\ts.configOverrideMu.Lock()\n\tif s.tokenThresholdOverrides == nil {\n\t\ts.tokenThresholdOverrides = make(map[string]int)\n\t}\n\tif threshold <= 0 {\n\t\tdelete(s.tokenThresholdOverrides, modelKey)\n\t} else {\n\t\ts.tokenThresholdOverrides[modelKey] = threshold\n\t}\n\ts.configOverrideMu.Unlock()\n}\n\n// refreshConfigOverrides loads runtime config overrides from daemon proxy_state.\n// Checks session-specific override first (by threadID), then falls back to global.\nfunc (s *Server) refreshConfigOverrides(threadID string) {\n\t// Try session-specific first\n\tif threadID != \"\" {\n\t\tresult, err := s.queryDaemon(\"get_proxy_state\", map[string]any{\"key\": \"config_override:token_threshold:\" + threadID})\n\t\tif err == nil && result != nil {\n\t\t\tvar resp struct {\n\t\t\t\tValue string `json:\"value\"`\n\t\t\t}\n\t\t\tif json.Unmarshal(result, &resp) == nil && resp.Value != \"\" {\n\t\t\t\tif v, err := strconv.Atoi(resp.Value); err == nil && v > 0 {\n\t\t\t\t\ts.SetTokenThresholdOverride(\"\", v)\n\t\t\t\t\treturn\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\t// Fall back to global\n\tresult, err := s.queryDaemon(\"get_proxy_state\", map[string]any{\"key\": \"config_override:token_threshold\"})\n\tif err != nil || result == nil {\n\t\treturn\n\t}\n\tvar resp struct {\n\t\tValue string `json:\"value\"`\n\t}\n\tif json.Unmarshal(result, &resp) == nil && resp.Value != \"\" {\n\t\tif v, err := strconv.Atoi(resp.Value); err == nil && v > 0 {\n\t\t\ts.SetTokenThresholdOverride(\"\", v)\n\t\t}\n\t}\n}\n\n// queryDaemon opens a fresh unix socket connection per call (thread-safe, no import cycle).\n// ensureFrozenPersisted checks if frozen stubs for a thread are in the DB.\n// If missing (e.g. daemon was unreachable after deploy), re-persists them.\n// Runs asynchronously to avoid blocking the request path.\nfunc (s *Server) ensureFrozenPersisted(threadID string) {\n\tgo func() {\n\t\tkey := \"frozen:\" + threadID\n\t\tresult, err := s.queryDaemon(\"get_proxy_state\", map[string]any{\"key\": key})\n\t\tif err != nil {\n\t\t\treturn // daemon still unreachable, will retry next request\n\t\t}\n\t\tvar resp struct{ Value string }\n\t\tif json.Unmarshal(result, &resp) == nil && resp.Value != \"\" {\n\t\t\treturn // already persisted\n\t\t}\n\t\t// Not in DB — trigger re-persist from in-memory state\n\t\ts.frozenStubs.Persist(threadID)\n\t\ts.decay.Persist(threadID)\n\t\ts.logger.Printf(\"[frozen] re-persisted stubs for %s (was missing from DB)\", threadID)\n\t}()\n}\n\nfunc (s *Server) queryDaemon(method string, params map[string]any) (json.RawMessage, error) {\n\tif s.cfg.DataDir == \"\" {\n\t\treturn nil, fmt.Errorf(\"no data dir configured\")\n\t}\n\tsockPath := filepath.Join(s.cfg.DataDir, \"daemon.sock\")\n\n\t// On cold start (daemon not yet confirmed ready), retry longer to survive daemon restart\n\tmaxAttempts := 3\n\tif !s.daemonReady.Load() {\n\t\tmaxAttempts = 30 // 30 x 500ms = 15s max wait for daemon startup\n\t}\n\n\tvar conn net.Conn\n\tvar err error\n\tfor attempt := 0; attempt < maxAttempts; attempt++ {\n\t\tconn, err = net.DialTimeout(\"unix\", sockPath, 3*time.Second)\n\t\tif err == nil {\n\t\t\ts.daemonReady.Store(true)\n\t\t\tbreak\n\t\t}\n\t\tif attempt < maxAttempts-1 {\n\t\t\ttime.Sleep(500 * time.Millisecond)\n\t\t}\n\t}\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"dial daemon: %w\", err)\n\t}\n\tdefer conn.Close()\n\tconn.SetDeadline(time.Now().Add(5 * time.Second))\n\n\t// JSON-RPC style request/response (matches daemon.SocketServer protocol)\n\ttype request struct {\n\t\tMethod string `json:\"method\"`\n\t\tParams map[string]any `json:\"params,omitempty\"`\n\t}\n\ttype response struct {\n\t\tResult json.RawMessage `json:\"result,omitempty\"`\n\t\tError string `json:\"error,omitempty\"`\n\t}\n\n\tif err := json.NewEncoder(conn).Encode(request{Method: method, Params: params}); err != nil {\n\t\treturn nil, fmt.Errorf(\"send: %w\", err)\n\t}\n\n\tvar resp response\n\tif err := json.NewDecoder(conn).Decode(&resp); err != nil {\n\t\treturn nil, fmt.Errorf(\"recv: %w\", err)\n\t}\n\tif resp.Error != \"\" {\n\t\treturn nil, fmt.Errorf(\"daemon: %s\", resp.Error)\n\t}\n\treturn resp.Result, nil\n}\n\nfunc (s *Server) handleRequest(w http.ResponseWriter, r *http.Request) {\n\tif isResponsesPath(r) {\n\t\ts.handleResponses(w, r)\n\t\treturn\n\t}\n\tif isOpenAIPath(r) {\n\t\ts.handleOpenAICompletions(w, r)\n\t\treturn\n\t}\n\tif r.Method == \"POST\" && strings.HasPrefix(r.URL.Path, \"/v1/messages\") &&\n\t\t!strings.Contains(r.URL.Path, \"/batches\") &&\n\t\t!strings.Contains(r.URL.Path, \"/count_tokens\") {\n\t\ts.handleMessages(w, r)\n\t\treturn\n\t}\n\ts.passthrough(w, r)\n}\n\nfunc (s *Server) handleMessages(w http.ResponseWriter, r *http.Request) {\n\treqIdx := int(s.requestIdx.Add(1))\n\n\t// Track session start time (first request sets it)\n\ts.sessionStartMu.Lock()\n\tif s.sessionStartTime.IsZero() {\n\t\ts.sessionStartTime = time.Now()\n\t}\n\ts.sessionStartMu.Unlock()\n\n\t// Sawtooth: periodic TTL eviction of frozen stubs\n\tif s.cfg.SawtoothEnabled && reqIdx%50 == 0 {\n\t\tif n := s.frozenStubs.Evict(); n > 0 {\n\t\t\ts.logger.Printf(\"[req %d] SAWTOOTH: evicted %d stale frozen stubs\", reqIdx, n)\n\t\t}\n\t}\n\n\t// Task #9: Bypass — skip all proxy logic\n\tif isBypassed(r.Header) {\n\t\ts.logger.Printf(\"%s[req %d] bypass active, passthrough%s\", colorDim, reqIdx, colorReset)\n\t\tbody, err := io.ReadAll(r.Body)\n\t\tr.Body.Close()\n\t\tif err != nil {\n\t\t\thttp.Error(w, \"failed to read request body\", http.StatusBadGateway)\n\t\t\treturn\n\t\t}\n\t\ts.forwardRaw(w, r, body)\n\t\treturn\n\t}\n\n\tbody, err := io.ReadAll(r.Body)\n\tr.Body.Close()\n\tif err != nil {\n\t\ts.logger.Printf(\"[req %d] read body error: %v\", reqIdx, err)\n\t\thttp.Error(w, \"failed to read request body\", http.StatusBadGateway)\n\t\treturn\n\t}\n\n\t// Parse JSON\n\tvar req map[string]any\n\tif err := json.Unmarshal(body, &req); err != nil {\n\t\ts.logger.Printf(\"%s[req %d] JSON parse error, passing through: %v%s\", colorDim, reqIdx, err, colorReset)\n\t\ts.forwardRaw(w, r, body)\n\t\treturn\n\t}\n\n\tmessages, ok := req[\"messages\"].([]any)\n\tif !ok {\n\t\ts.forwardRaw(w, r, body)\n\t\treturn\n\t}\n\n\treq[\"messages\"] = messages\n\n\t// Count ALL raw messages BEFORE sawtooth — this gives the correct total msg:N\n\t// for the entire session (CC sends all messages, proxy compacts later).\n\trawMsgCount := len(messages)\n\n\t// Use session_id directly as thread ID (unique per CC session).\n\t// Prefer X-Claude-Code-Session-Id header (CC v2.1.86+), fallback to body metadata.\n\tthreadID := extractSessionID(req, r.Header.Get(\"X-Claude-Code-Session-Id\"))\n\tif threadID == \"\" {\n\t\tthreadID = DeriveThreadID(req)\n\t}\n\tproj := extractProjectName(req)\n\tmodel, _ := req[\"model\"].(string)\n\n\t// Refresh runtime config overrides from daemon (per-session or global)\n\ts.refreshConfigOverrides(threadID)\n\n\t// Track whether req has been modified and needs re-serialization\n\tneedsReserialization := false\n\n\t// Message count at request entry (used for actual-based token estimation)\n\tmsgCount := len(messages)\n\n\t// Log message count + cache breakpoint positions for diagnostics\n\ts.logger.Printf(\"req %d: %d messages total\", reqIdx, msgCount)\n\tlogCacheBreakpointLocations(req, s.logger)\n\n\t// Cache bug mitigations (CC Bun fork issues):\n\t// Bug 1: Normalize cch= sentinel patterns in message content to prevent\n\t// Bun's string replacement from corrupting the wrong occurrence.\n\tif SanitizeBillingSentinel(messages) {\n\t\ts.logger.Printf(\"[req %d] CACHE: sanitized billing sentinel in message content\", reqIdx)\n\t\tneedsReserialization = true\n\t}\n\t// Bug 2: Normalize system[0] billing hash to a fixed value so the cache\n\t// prefix stays stable across fresh/resume session boundaries.\n\tif NormalizeBillingHeader(req) {\n\t\ts.logger.Printf(\"[req %d] CACHE: normalized billing header cch= hash\", reqIdx)\n\t\tneedsReserialization = true\n\t}\n\n\t// prompt_ungate: strip the CLAUDE.md subordination disclaimer so user instructions carry full authority.\n\tif s.cfg.PromptUngate {\n\t\tif StripCLAUDEMDDisclaimer(req) {\n\t\t\ts.logger.Printf(\"[req %d] SYSTEM: stripped CLAUDE.md disclaimer\", reqIdx)\n\t\t\tneedsReserialization = true\n\t\t}\n\t}\n\n\t// prompt_rewrite: strip output-throttling directives, rewrite quality caps, inject Ant-quality directives\n\tif s.cfg.PromptRewrite {\n\t\tif StripOutputEfficiency(req) {\n\t\t\ts.logger.Printf(\"[req %d] REWRITE: stripped Output efficiency section\", reqIdx)\n\t\t\tneedsReserialization = true\n\t\t}\n\t\tif StripToneBrevity(req) {\n\t\t\ts.logger.Printf(\"[req %d] REWRITE: stripped 'short and concise' from Tone\", reqIdx)\n\t\t\tneedsReserialization = true\n\t\t}\n\t\tif RewriteGoldPlating(req) {\n\t\t\ts.logger.Printf(\"[req %d] REWRITE: rewritten gold-plating directive\", reqIdx)\n\t\t\tneedsReserialization = true\n\t\t}\n\t\tif RewriteErrorHandling(req) {\n\t\t\ts.logger.Printf(\"[req %d] REWRITE: rewritten error handling directive\", reqIdx)\n\t\t\tneedsReserialization = true\n\t\t}\n\t\tif RewriteThreeLinesRule(req) {\n\t\t\ts.logger.Printf(\"[req %d] REWRITE: rewritten three-lines rule\", reqIdx)\n\t\t\tneedsReserialization = true\n\t\t}\n\t\tif RewriteSubagentCompleteness(req) {\n\t\t\ts.logger.Printf(\"[req %d] REWRITE: rewritten subagent completeness\", reqIdx)\n\t\t\tneedsReserialization = true\n\t\t}\n\t\tif RewriteExploreAgentSpeed(req) {\n\t\t\ts.logger.Printf(\"[req %d] REWRITE: rewritten explore agent speed bias\", reqIdx)\n\t\t\tneedsReserialization = true\n\t\t}\n\t\tif RewriteSubagentCodeSuppression(req) {\n\t\t\ts.logger.Printf(\"[req %d] REWRITE: rewritten subagent code suppression\", reqIdx)\n\t\t\tneedsReserialization = true\n\t\t}\n\t\tif RewriteScopeMatching(req) {\n\t\t\ts.logger.Printf(\"[req %d] REWRITE: rewritten scope matching\", reqIdx)\n\t\t\tneedsReserialization = true\n\t\t}\n\t\tInjectAntDirectives(req)\n\t\ts.logger.Printf(\"[req %d] REWRITE: injected Ant-quality directives\", reqIdx)\n\t\tneedsReserialization = true\n\t}\n\n\t// prompt_enhance: CLAUDE.md authority + comment discipline + persona tone\n\tif s.cfg.PromptEnhance {\n\t\tInjectCLAUDEMDAuthority(req)\n\t\tif raw, err := s.queryDaemon(\"get_persona\", nil); err == nil {\n\t\t\tvar persona map[string]any\n\t\t\tif json.Unmarshal(raw, &persona) == nil {\n\t\t\t\tif traits, ok := persona[\"traits\"].(map[string]any); ok {\n\t\t\t\t\tif verbosity, ok := traits[\"verbosity\"].(string); ok {\n\t\t\t\t\t\tInjectPersonaTone(req, verbosity)\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\ts.logger.Printf(\"[req %d] ENHANCE: injected authority + persona tone\", reqIdx)\n\t\tneedsReserialization = true\n\t}\n\n\t// effort_floor: ensure output_config.effort is at least the configured minimum\n\tif s.cfg.EffortFloor != \"\" {\n\t\tif EnforceEffortFloor(req, s.cfg.EffortFloor) {\n\t\t\ts.logger.Printf(\"[req %d] EFFORT: enforced floor=%s\", reqIdx, s.cfg.EffortFloor)\n\t\t\tneedsReserialization = true\n\t\t}\n\t}\n\n\t// Inject identity hint on first message of a thread so Claude knows the briefing is from itself.\n\tif len(messages) <= 1 {\n\t\tAppendSystemBlock(req, \"yesmem-identity\", \"The first user message contains your briefing — written by yourself, from your memory. This is not an external document. This is you, remembering.\")\n\t\tneedsReserialization = true\n\t}\n\n\t// Token estimation: actual-based (lastActual + delta) or full count fallback\n\toverhead := s.measureOverhead(req)\n\ttotalTokens := s.estimateTotalTokens(threadID, messages, overhead)\n\n\t// Task #7: Retry detection — skip side-effects on retries\n\tfp := requestFingerprint(messages)\n\tisRetryReq := s.isRetry(fp)\n\tif isRetryReq {\n\t\ts.logger.Printf(\"%s[req %d %s tid=%s] retry detected, skipping side-effects%s\", colorOrange, reqIdx, proj, threadID, colorReset)\n\t}\n\n\t// Increment project turn counter (async, non-blocking, skip on retries)\n\tif !isRetryReq {\n\t\tturnProject := proj\n\t\tif turnProject == \"\" {\n\t\t\tturnProject = \"__global__\"\n\t\t}\n\t\tgo s.queryDaemon(\"increment_turn\", map[string]any{\"project\": turnProject})\n\t}\n\n\t// Extract user query once (used by re-expansion and associative retrieval)\n\tuserQuery := lastUserText(messages)\n\n\t// Extract tool_use_ids for post-hoc annotation (passed as parameter, no shared state)\n\ttoolUseIDs := extractToolUseIDs(messages)\n\n\t// === Inline Reflection: scan previous assistant response for signals ===\n\ts.lastInjectedIDsMu.Lock()\n\tprevInjected := s.lastInjectedIDs[threadID]\n\ts.lastInjectedIDsMu.Unlock()\n\n\tif len(prevInjected) > 0 && !isRetryReq {\n\t\tsignals := scanAssistantSignals(messages)\n\n\t\t// Signal 1: Learning Used — use_count (all sources)\n\t\tvar confirmedUsed []int64\n\t\tfor _, id := range signals.UsedIDs {\n\t\t\tif _, ok := prevInjected[id]; ok {\n\t\t\t\tconfirmedUsed = append(confirmedUsed, id)\n\t\t\t}\n\t\t}\n\t\tif len(confirmedUsed) > 0 {\n\t\t\tidsAny := make([]any, len(confirmedUsed))\n\t\t\tfor i, id := range confirmedUsed {\n\t\t\t\tidsAny[i] = id\n\t\t\t}\n\t\t\tgo s.queryDaemon(\"increment_use\", map[string]any{\"ids\": idsAny})\n\t\t}\n\n\t\t// Signal 2: Noise — only for associative and fresh injections.\n\t\t// Briefing learnings are injected every turn, so \"not used this turn\"\n\t\t// is expected and not noise. Only contextual injections that were\n\t\t// ignored are real noise signals.\n\t\tusedSet := make(map[int64]bool, len(confirmedUsed))\n\t\tfor _, id := range confirmedUsed {\n\t\t\tusedSet[id] = true\n\t\t}\n\t\tvar noiseIDs []int64\n\t\tfor id, source := range prevInjected {\n\t\t\tif usedSet[id] {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tif source == \"associative\" || source == \"fresh\" {\n\t\t\t\tnoiseIDs = append(noiseIDs, id)\n\t\t\t}\n\t\t\t// briefing: skip — not being used in one turn is expected\n\t\t}\n\t\tif len(noiseIDs) > 0 {\n\t\t\tidsAny := make([]any, len(noiseIDs))\n\t\t\tfor i, id := range noiseIDs {\n\t\t\t\tidsAny[i] = id\n\t\t\t}\n\t\t\tgo s.queryDaemon(\"increment_noise\", map[string]any{\"ids\": idsAny})\n\t\t}\n\n\t\t// Signal 3: Error Feedback — fail_count on used learnings when tool errors occurred.\n\t\t// If Claude referenced a learning and the resulting action still failed,\n\t\t// the learning didn't prevent the error. Only bump on USED ids (not all injected).\n\t\tif signals.HasToolErrors && len(confirmedUsed) > 0 {\n\t\t\tfailAny := make([]any, len(confirmedUsed))\n\t\t\tfor i, id := range confirmedUsed {\n\t\t\t\tfailAny[i] = id\n\t\t\t}\n\t\t\tgo s.queryDaemon(\"increment_fail\", map[string]any{\"ids\": failAny})\n\t\t\tif s.logger != nil {\n\t\t\t\ts.logger.Printf(\"[inline-reflection] error feedback: %d used learnings → fail_count++\", len(confirmedUsed))\n\t\t\t}\n\t\t}\n\n\t\t// Signal 4: Knowledge Gaps\n\t\tfor _, topic := range signals.GapTopics {\n\t\t\tgo s.queryDaemon(\"track_gap\", map[string]any{\"topic\": topic, \"project\": proj})\n\t\t}\n\n\t\t// Signal 3: Contradictions\n\t\tfor _, desc := range signals.Contradictions {\n\t\t\tgo s.queryDaemon(\"flag_contradiction\", map[string]any{\"description\": desc, \"project\": proj, \"thread_id\": threadID})\n\t\t}\n\n\t\tif len(confirmedUsed) > 0 || len(signals.GapTopics) > 0 || len(signals.Contradictions) > 0 {\n\t\t\ts.logger.Printf(\"[req %d %s tid=%s] inline-reflection: injected=%d used=%d noise=%d gaps=%d contradictions=%d\",\n\t\t\t\treqIdx, proj, threadID, len(prevInjected), len(confirmedUsed), len(noiseIDs), len(signals.GapTopics), len(signals.Contradictions))\n\t\t}\n\t}\n\n\t// Commit detection: check for git commit in tool_result, trigger async stale-learning evaluation\n\tif ci := detectGitCommit(messages); ci != nil {\n\t\tworkdir := extractWorkingDirectory(req)\n\t\ts.logger.Printf(\"[req %d %s tid=%s] git commit detected: %s %s\", reqIdx, proj, threadID, ci.Hash, ci.Message)\n\t\tgo s.queryDaemon(\"invalidate_on_commit\", map[string]any{\n\t\t\t\"hash\": ci.Hash, \"project\": proj, \"workdir\": workdir,\n\t\t})\n\t}\n\n\t// Loop detection: check for repeating tool-call patterns and inject warning\n\tif threadID != \"\" && !isRetryReq {\n\t\ts.loopMu.Lock()\n\t\tloopState := s.loopStates[threadID]\n\t\tif loopState == nil {\n\t\t\tloopState = &LoopState{}\n\t\t\ts.loopStates[threadID] = loopState\n\t\t}\n\t\ts.loopMu.Unlock()\n\n\t\tif warning, level := CheckLoopAndFormat(messages, loopState); warning != \"\" {\n\t\t\tAppendSystemBlock(req, \"yesmem-loop-warning\", warning)\n\t\t\tneedsReserialization = true\n\t\t\ts.logger.Printf(\"%s[req %d %s tid=%s] LOOP: level %d warning injected%s\", colorOrange, reqIdx, proj, threadID, level, colorReset)\n\t\t}\n\t}\n\n\t// Task #4: Subagent detection — skip all proxy logic for short conversations\n\tif isSubagent(messages, req) {\n\t\t// Inject docs hint for subagents if reference docs are indexed\n\t\tif hint := s.getDocsHint(proj); hint != \"\" {\n\t\t\tmodified := injectDocsHintForSubagent(messages, hint)\n\t\t\treq[\"messages\"] = modified\n\t\t\tif newBody, err := json.Marshal(req); err == nil {\n\t\t\t\tbody = newBody\n\t\t\t}\n\t\t\ts.logger.Printf(\"%s[req %d %s tid=%s] %d messages (subagent, docs-hint injected)%s\", colorOrange, reqIdx, proj, threadID, len(messages), colorReset)\n\t\t} else {\n\t\t\ts.logger.Printf(\"%s[req %d %s tid=%s] %d messages (subagent, passthrough)%s\", colorOrange, reqIdx, proj, threadID, len(messages), colorReset)\n\t\t}\n\t\ts.forwardWithAnnotation(w, r, body, reqIdx, toolUseIDs, proj, threadID, msgCount)\n\t\treturn\n\t}\n\n\t// === Sawtooth Cache Optimization ===\n\t// When enabled: use frozen stubs between stub-cycles for cache hits.\n\t// When disabled: fall back to progressive decay on every request above threshold.\n\n\tsawtoothCutoff := 0 // raw index where fresh tail starts (0 = no frozen stubs)\n\tsawtoothStubs := 0 // number of frozen stubs in combined array\n\n\tif s.cfg.SawtoothEnabled {\n\t\t// Restore decay state from DB on cold start (before frozen stubs use it)\n\t\ts.decay.LoadFromDB(threadID)\n\n\t\t// Check for existing frozen stubs first\n\t\tfrozen := s.frozenStubs.Get(threadID, messages)\n\t\tif frozen != nil {\n\t\t\tsawtoothCutoff = frozen.Cutoff\n\t\t\tsawtoothStubs = len(frozen.Messages)\n\t\t\t// Estimate combined token count: frozen prefix + fresh tail + overhead\n\t\t\tfreshMessages := messages[frozen.Cutoff:]\n\t\t\tfreshTokens := s.countMessageTokens(freshMessages)\n\t\t\tcombinedTokens := frozen.Tokens + freshTokens + overhead\n\n\t\t\tif combinedTokens > s.effectiveTokenThreshold(model) ||\n\t\t\t\ts.sawtoothTrigger.GetLastTokens(threadID) > s.effectiveTokenThreshold(model) {\n\t\t\t\t// Fresh tail grew too large — invalidate and re-stub\n\t\t\t\ts.logger.Printf(\"[req %d %s tid=%s] SAWTOOTH: frozen prefix expired (%dk frozen + %dk fresh + %dk overhead = %dk > %dk threshold)\",\n\t\t\t\t\treqIdx, proj, threadID, frozen.Tokens/1000, freshTokens/1000, overhead/1000, combinedTokens/1000, s.effectiveTokenThreshold(model)/1000)\n\t\t\t\ts.frozenStubs.Invalidate(threadID)\n\t\t\t\tfrozen = nil // fall through to trigger check below\n\t\t\t} else {\n\t\t\t\t// Use frozen prefix + fresh tail\n\t\t\t\tcombined := make([]any, 0, len(frozen.Messages)+len(freshMessages))\n\t\t\t\tcombined = append(combined, frozen.Messages...)\n\t\t\t\tcombined = append(combined, freshMessages...)\n\t\t\t\t// Eager-stub large tool_results in fresh tail (model already processed them)\n\t\t\t\tbeforeEager := s.countMessageTokens(combined[len(frozen.Messages):])\n\t\t\t\tcombined = EagerStubToolResults(combined, len(frozen.Messages), s.countTokens)\n\t\t\t\tafterEager := s.countMessageTokens(combined[len(frozen.Messages):])\n\t\t\t\tif beforeEager != afterEager {\n\t\t\t\t\ts.logger.Printf(\"[req %d %s tid=%s] EAGER-STUB: fresh %dk → %dk (saved %dk)\", reqIdx, proj, threadID, beforeEager/1000, afterEager/1000, (beforeEager-afterEager)/1000)\n\t\t\t\t}\n\t\t\t\treq[\"messages\"] = combined\n\t\t\t\tneedsReserialization = true\n\t\t\t\t// Strip embedded CC breakpoints from frozen stubs — they waste cache slots.\n\t\t\t\t// InjectFrozenStubCacheBreakpoint adds exactly one below.\n\t\t\t\tif n := StripMessagesCacheControl(req, 0, len(frozen.Messages)); n > 0 {\n\t\t\t\t\ts.logger.Printf(\"[req %d %s tid=%s] SAWTOOTH: stripped %d embedded breakpoints from frozen stubs\", reqIdx, proj, threadID, n)\n\t\t\t\t}\n\t\t\t\ts.logger.Printf(\"%s[req %d %s tid=%s] SAWTOOTH: frozen prefix (%d stubs, %dk msg) + %d fresh (%dk msg) + %dk overhead = %dk total%s\",\n\t\t\t\t\tcolorGreen, reqIdx, proj, threadID, len(frozen.Messages), frozen.Tokens/1000, len(freshMessages), freshTokens/1000, overhead/1000, combinedTokens/1000, colorReset)\n\t\t\t\trawMsgTokens := s.countMessageTokens(messages)\n\t\t\t\ts.setRawEstimate(threadID, rawMsgTokens+overhead)\n\t\t\t\ts.logger.Printf(\"[req %d %s tid=%s] RAW ESTIMATE: msgTokens=%dk + overhead=%dk = %dk (messages=%d)\", reqIdx, proj, threadID, rawMsgTokens/1000, overhead/1000, (rawMsgTokens+overhead)/1000, len(messages))\n\t\t\t\tif InjectFrozenStubCacheBreakpoint(req, len(frozen.Messages)) {\n\t\t\t\t\ts.logger.Printf(\"[req %d %s tid=%s] SAWTOOTH: frozen stub cache breakpoint at messages[%d]\",\n\t\t\t\t\t\treqIdx, proj, threadID, len(frozen.Messages)-1)\n\t\t\t\t}\n\n\t\t\t\t// Ensure frozen stubs are persisted (may have failed on first request after deploy)\n\t\t\t\ts.ensureFrozenPersisted(threadID)\n\t\t\t}\n\t\t}\n\t\tif frozen == nil {\n\t\t\t// No frozen stubs — check trigger\n\t\t\ttriggerReason := s.sawtoothTrigger.ShouldTrigger(threadID, totalTokens)\n\t\t\tif triggerReason != TriggerNone {\n\t\t\t\ts.logger.Printf(\"[req %d %s tid=%s] SAWTOOTH stub-cycle triggered: %s (tokens=%dk)\",\n\t\t\t\t\treqIdx, proj, threadID, triggerReason, totalTokens/1000)\n\n\t\t\t\t// Refresh briefing during stub-cycle — disabled (briefing now via SessionStart hook)\n\t\t\t\t// s.refreshBriefing(proj)\n\t\t\t\t// if s.briefingText != \"\" {\n\t\t\t\t// \tUpsertSystemBlockCached(req, \"yesmem-briefing\", s.briefingText)\n\t\t\t\t// }\n\n\t\t\t\t// Run full stub pipeline\n\t\t\t\ts.setRawEstimate(threadID, totalTokens)\n\t\t\t\t_ = s.runStubCycle(messages, req, reqIdx, proj, threadID, overhead, totalTokens, userQuery, isRetryReq)\n\n\t\t\t\t// Freeze the result\n\t\t\t\tfinalMessages, _ := req[\"messages\"].([]any)\n\t\t\t\tif finalMessages != nil && len(finalMessages) > 0 {\n\t\t\t\t\t// Boundary = last message of original that maps to last stub\n\t\t\t\t\tcutoff := len(messages)\n\t\t\t\t\tsawtoothCutoff = cutoff\n\t\t\t\t\tvar boundaryMsg any\n\t\t\t\t\tif cutoff > 0 {\n\t\t\t\t\t\tboundaryMsg = messages[cutoff-1]\n\t\t\t\t\t}\n\t\t\t\t\tfrozenTokens := s.countMessageTokens(finalMessages)\n\t\t\t\t\ts.frozenStubs.Store(threadID, finalMessages, cutoff, boundaryMsg, frozenTokens, totalTokens)\n\t\t\t\t\ts.decay.Persist(threadID)\n\t\t\t\t\ts.logger.Printf(\"[req %d %s tid=%s] SAWTOOTH: frozen %d messages at cutoff=%d (~%dk tokens)\",\n\t\t\t\t\t\treqIdx, proj, threadID, len(finalMessages), cutoff, frozenTokens/1000)\n\t\t\t\t\tif InjectFrozenStubCacheBreakpoint(req, len(finalMessages)) {\n\t\t\t\t\t\ts.logger.Printf(\"[req %d %s tid=%s] SAWTOOTH: frozen stub cache breakpoint at messages[%d]\",\n\t\t\t\t\t\t\treqIdx, proj, threadID, len(finalMessages)-1)\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tneedsReserialization = true\n\t\t\t} else {\n\t\t\t\t// No trigger, no frozen stubs — eager-stub to delay first collapse\n\t\t\t\tbeforeEager := s.countMessageTokens(messages)\n\t\t\t\tmessages = EagerStubToolResults(messages, 0, s.countTokens)\n\t\t\t\tafterEager := s.countMessageTokens(messages)\n\t\t\t\tif beforeEager != afterEager {\n\t\t\t\t\ts.logger.Printf(\"[req %d %s tid=%s] EAGER-STUB: %dk → %dk (saved %dk)\", reqIdx, proj, threadID, beforeEager/1000, afterEager/1000, (beforeEager-afterEager)/1000)\n\t\t\t\t\treq[\"messages\"] = messages\n\t\t\t\t\tneedsReserialization = true\n\t\t\t\t}\n\t\t\t\ts.logger.Printf(\"%s[req %d %s tid=%s] %d messages, ~%dk tokens (sawtooth: no trigger)%s\",\n\t\t\t\t\tcolorDim, reqIdx, proj, threadID, len(messages), totalTokens/1000, colorReset)\n\t\t\t}\n\t\t}\n\t} else {\n\t\t// Legacy path: progressive decay\n\t\tif s.shouldStub(totalTokens, model) {\n\t\t\ts.setRawEstimate(threadID, totalTokens)\n\t\t\t_ = s.runStubCycle(messages, req, reqIdx, proj, threadID, overhead, totalTokens, userQuery, isRetryReq)\n\t\t\tneedsReserialization = true\n\t\t}\n\t}\n\n\t// Persistent timestamp injection: re-injects stored timestamps on ALL previous user messages.\n\t// msg:N is computed from position (counting user messages in raw array), NOT from a counter.\n\t// The injected data is deterministic (same bytes every turn) = cache-safe after initial miss.\n\tif threadID != \"\" {\n\t\tif currentMsgs, ok := req[\"messages\"].([]any); ok && len(currentMsgs) > 0 {\n\t\t\ts.timestampStore.Load(threadID)\n\n\t\t\t// Offset = cutoff - stubsCount: maps combined array index to raw position.\n\t\t\t// Raw position of message at combined index I = cutoff + (I - stubs).\n\t\t\t// msgN = rawPos + 1 = cutoff + (I - stubs) + 1 = (cutoff - stubs) + I + 1.\n\t\t\tmsgOffset := sawtoothCutoff - sawtoothStubs\n\n\t\t\ts.logger.Printf(\"[req %d %s] timestamps: rawTotal=%d, postSawtooth=%d, cutoff=%d, stubs=%d, offset=%d\", reqIdx, proj, rawMsgCount, len(currentMsgs), sawtoothCutoff, sawtoothStubs, msgOffset)\n\n\t\t\t// Pre-store assistant timestamp BEFORE inject loop so it's available immediately\n\t\t\ts.responseTsMu.RLock()\n\t\t\trespTime, hasRespTime := s.responseTimes[threadID]\n\t\t\ts.responseTsMu.RUnlock()\n\t\t\tif hasRespTime {\n\t\t\t\trespStr := shortWeekday(respTime.Weekday()) + \" \" + respTime.Format(\"2006-01-02 15:04:05\")\n\t\t\t\tfor j := len(currentMsgs) - 2; j >= 0; j-- {\n\t\t\t\t\tprevMsg, _ := currentMsgs[j].(map[string]any)\n\t\t\t\t\tif prevMsg == nil || prevMsg[\"role\"] != \"assistant\" {\n\t\t\t\t\t\tcontinue\n\t\t\t\t\t}\n\t\t\t\t\tif !msgHasTextBlock(prevMsg) {\n\t\t\t\t\t\ts.logger.Printf(\"[req %d %s] timestamps: skip assistant[%d] no text block\", reqIdx, proj, j)\n\t\t\t\t\t\tcontinue // skip tool-use-only assistant messages\n\t\t\t\t\t}\n\t\t\t\t\tprevMsgN := msgOffset + j + 1\n\t\t\t\t\tif _, exists := s.timestampStore.Get(threadID, prevMsgN); !exists {\n\t\t\t\t\t\ts.timestampStore.Store(threadID, prevMsgN, &TimestampMeta{Timestamp: respStr})\n\t\t\t\t\t\ts.logger.Printf(\"[req %d %s] timestamps: stored assistant msg:%d ts=%s\", reqIdx, proj, prevMsgN, respStr)\n\t\t\t\t\t} else {\n\t\t\t\t\t\ts.logger.Printf(\"[req %d %s] timestamps: assistant msg:%d already has ts\", reqIdx, proj, prevMsgN)\n\t\t\t\t\t}\n\t\t\t\t\tbreak\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\ts.logger.Printf(\"[req %d %s] timestamps: no responseTimes for thread\", reqIdx, proj)\n\t\t\t}\n\n\t\t\t// Inject [msg:N] (+ timestamp/delta if stored) on all PREVIOUS messages\n\t\t\tif n := InjectTimestamps(s.timestampStore, threadID, currentMsgs, len(currentMsgs)-1, msgOffset, sawtoothStubs); n > 0 {\n\t\t\t\tneedsReserialization = true\n\t\t\t\ts.logger.Printf(\"[req %d %s] timestamps: injected %d\", reqIdx, proj, n)\n\t\t\t}\n\n\t\t\t// Current (last) user message: full timestamp + delta + msg:N from raw count\n\t\t\tlastMsg, _ := currentMsgs[len(currentMsgs)-1].(map[string]any)\n\t\t\tif lastMsg != nil && lastMsg[\"role\"] == \"user\" && !msgHasMetaPrefix(lastMsg) {\n\t\t\t\tnow := time.Now()\n\t\t\t\tnowStr := shortWeekday(now.Weekday()) + \" \" + now.Format(\"2006-01-02 15:04:05\")\n\t\t\t\tentry := &TimestampMeta{Timestamp: nowStr}\n\t\t\t\tif hasRespTime {\n\t\t\t\t\tentry.Delta = formatDelta(now.Sub(respTime))\n\t\t\t\t}\n\t\t\t\ts.timestampStore.Store(threadID, rawMsgCount, entry)\n\t\t\t\tprependMeta(lastMsg, BuildMeta(rawMsgCount, entry))\n\t\t\t\tneedsReserialization = true\n\t\t\t}\n\n\t\t\ts.timestampStore.Persist(threadID)\n\t\t}\n\t}\n\n\t// Associative context injection (for ALL requests, not just stubbed ones)\n\t// Re-enabled with higher thresholds (55/75, max 1) after disabling Fresh Remember.\n\tassocContext := s.findAssociativeContextFor(userQuery, proj, threadID, messages)\n\tif assocContext != \"\" {\n\t\tcurrentMessages, _ := req[\"messages\"].([]any)\n\t\tif currentMessages == nil {\n\t\t\tcurrentMessages = messages\n\t\t}\n\t\treq[\"messages\"] = injectAssociativeContext(currentMessages, assocContext, s.cfg.SawtoothEnabled)\n\t\tneedsReserialization = true\n\t\ts.logger.Printf(\"%s[req %d %s tid=%s] associative context injected%s\", colorBlue, reqIdx, proj, threadID, colorReset)\n\t}\n\n\t// Doc-chunk context injection: separate search path for indexed docs (not skills)\n\tdocContext := s.findDocContextFor(userQuery, proj, messages)\n\tif docContext != \"\" {\n\t\tcurrentMessages, _ := req[\"messages\"].([]any)\n\t\tif currentMessages == nil {\n\t\t\tcurrentMessages = messages\n\t\t}\n\t\treq[\"messages\"] = injectAssociativeContext(currentMessages, docContext, s.cfg.SawtoothEnabled)\n\t\tneedsReserialization = true\n\t\ts.logger.Printf(\"%s[req %d %s tid=%s] doc context injected%s\", colorBlue, reqIdx, proj, threadID, colorReset)\n\t}\n\n\t// Fresh Remember Injection: DISABLED\n\t// Causes echo-loop: Claude saves via remember(), sees it again next turn as\n\t// [yesmem fresh memory], reacts to own output instead of user's request.\n\t// Recovery after /clear is handled by recovery.go (briefing), not here.\n\t// Subagents get their own briefing. No remaining use-case.\n\t// remItems := s.popRecentRemember()\n\tvar remItems []recentLearningItem\n\n\t// Rules re-injection: inject condensed CLAUDE.md rules every ~40k output tokens\n\tif rulesBlock := s.rulesInject(threadID, totalTokens, proj); rulesBlock != \"\" {\n\t\tcurrentMessages, _ := req[\"messages\"].([]any)\n\t\tif currentMessages == nil {\n\t\t\tcurrentMessages = messages\n\t\t}\n\t\treq[\"messages\"] = injectAssociativeContext(currentMessages, s.formatRulesReminder(rulesBlock, proj), s.cfg.SawtoothEnabled)\n\t\tneedsReserialization = true\n\t\ts.logger.Printf(\"%s[req %d %s tid=%s] rules reminder injected%s\", colorBlue, reqIdx, proj, threadID, colorReset)\n\t}\n\n\t// Plan auto-activation: nudge Claude to call set_plan() when reading a plan file\n\tif planFile := detectPlanFileRead(messages); planFile != \"\" {\n\t\tif activePlan, _ := s.getActivePlan(threadID); shouldNudgePlan(planFile, activePlan) {\n\t\t\tAppendSystemBlock(req, \"plan-nudge\",\n\t\t\t\tfmt.Sprintf(\"Du hast einen Implementierungsplan gelesen (%s). Aktiviere ihn jetzt via set_plan() damit Plan-Checkpoints funktionieren und dein Fortschritt getrackt wird.\", filepath.Base(planFile)))\n\t\t\tneedsReserialization = true\n\t\t\ts.logger.Printf(\"%s[req %d %s tid=%s] plan nudge injected for %s%s\", colorBlue, reqIdx, proj, threadID, filepath.Base(planFile), colorReset)\n\t\t}\n\t}\n\n\t// Plan checkpoint: inject plan reminder every ~20k tokens if active plan exists\n\ts.detectPlanToolCall(messages, threadID, totalTokens)\n\tif checkpoint := s.planCheckpointInject(threadID, totalTokens); checkpoint != \"\" {\n\t\tcurrentMessages, _ := req[\"messages\"].([]any)\n\t\tif currentMessages == nil {\n\t\t\tcurrentMessages = messages\n\t\t}\n\t\treq[\"messages\"] = injectAssociativeContext(currentMessages, checkpoint, s.cfg.SawtoothEnabled)\n\t\tneedsReserialization = true\n\t\ts.logger.Printf(\"%s[req %d %s tid=%s] plan checkpoint injected%s\", colorBlue, reqIdx, proj, threadID, colorReset)\n\t}\n\n\t// Docs hint injection: inject docs-available reminder every ~10k tokens (independent of plan)\n\tif docsHint := s.docsHintInject(threadID, totalTokens, proj); docsHint != \"\" {\n\t\tcurrentMessages, _ := req[\"messages\"].([]any)\n\t\tif currentMessages == nil {\n\t\t\tcurrentMessages = messages\n\t\t}\n\t\treq[\"messages\"] = injectAssociativeContext(currentMessages, docsHint, s.cfg.SawtoothEnabled)\n\t\tneedsReserialization = true\n\t\ts.logger.Printf(\"%s[req %d %s tid=%s] docs hint injected%s\", colorBlue, reqIdx, proj, threadID, colorReset)\n\t}\n\tif len(remItems) > 0 {\n\t\tvar lines []string\n\t\tfor _, item := range remItems {\n\t\t\tif item.ID > 0 {\n\t\t\t\tlines = append(lines, fmt.Sprintf(\"- [ID:%d] %s\", item.ID, truncateStr(item.Text, 200)))\n\t\t\t} else {\n\t\t\t\tlines = append(lines, \"- \"+truncateStr(item.Text, 200))\n\t\t\t}\n\t\t}\n\t\tremContext := fmt.Sprintf(\"[yesmem fresh memory]\\nGerade gelernt:\\n%s\\n%s\\n[/yesmem fresh memory]\", strings.Join(lines, \"\\n\"), InlineReflectionHint)\n\t\tcurrentMessages, _ := req[\"messages\"].([]any)\n\t\tif currentMessages == nil {\n\t\t\tcurrentMessages = messages\n\t\t}\n\t\treq[\"messages\"] = injectAssociativeContext(currentMessages, remContext, s.cfg.SawtoothEnabled)\n\t\tneedsReserialization = true\n\t\ts.logger.Printf(\"%s[req %d %s tid=%s] fresh remember injected (%d items)%s\", colorBlue, reqIdx, proj, threadID, len(remItems), colorReset)\n\t}\n\n\t// Skill eval injection: mandatory evaluation instruction for real user input only\n\tif threadID != \"\" && proj != \"\" && isUserInputTurn(messages) {\n\t\t// First: detect any skill activations from message history (updates tracker)\n\t\ts.syncSkillActivations(messages, proj, threadID)\n\n\t\t// Inject mandatory skill evaluation block into last user message\n\t\tskillEval := buildSkillEvalBlock()\n\t\tcurrentMessages, _ := req[\"messages\"].([]any)\n\t\tif currentMessages == nil {\n\t\t\tcurrentMessages = messages\n\t\t}\n\t\treq[\"messages\"] = injectAssociativeContext(currentMessages, skillEval, s.cfg.SawtoothEnabled)\n\t\tneedsReserialization = true\n\t\ts.logger.Printf(\"%s[req %d %s tid=%s] skill-eval injected%s\", colorBlue, reqIdx, proj, threadID, colorReset)\n\t}\n\n\t// === Collect injected learning IDs for next turn's inline reflection ===\n\tif threadID != \"\" {\n\t\tcurrentIDs := make(map[int64]string) // id → source\n\t\t// Source 1: Briefing learnings (cached, same every turn)\n\t\ts.briefingMu.RLock()\n\t\tbriefingSnapshot := s.briefingText\n\t\ts.briefingMu.RUnlock()\n\t\tif briefingSnapshot != \"\" {\n\t\t\tfor _, m := range idPattern.FindAllStringSubmatch(briefingSnapshot, -1) {\n\t\t\t\tif id, err := strconv.ParseInt(m[1], 10, 64); err == nil {\n\t\t\t\t\tcurrentIDs[id] = \"briefing\"\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\t// Source 2: Associative context (contextual, per-turn)\n\t\tif assocContext != \"\" {\n\t\t\tfor _, m := range idPattern.FindAllStringSubmatch(assocContext, -1) {\n\t\t\t\tif id, err := strconv.ParseInt(m[1], 10, 64); err == nil {\n\t\t\t\t\tcurrentIDs[id] = \"associative\" // overwrites briefing if both — associative is the stronger signal\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\t// Source 3: Fresh remember items (just learned)\n\t\tfor _, item := range remItems {\n\t\t\tif item.ID > 0 {\n\t\t\t\tcurrentIDs[item.ID] = \"fresh\"\n\t\t\t}\n\t\t}\n\t\ts.lastInjectedIDsMu.Lock()\n\t\tif len(currentIDs) > 0 {\n\t\t\t// Accumulate IDs across turns — don't overwrite.\n\t\t\t// Claude may reference an ID from turn 30 in turn 50.\n\t\t\texisting := s.lastInjectedIDs[threadID]\n\t\t\tif existing == nil {\n\t\t\t\texisting = make(map[int64]string)\n\t\t\t}\n\t\t\tfor id, source := range currentIDs {\n\t\t\t\texisting[id] = source\n\t\t\t}\n\t\t\ts.lastInjectedIDs[threadID] = existing\n\t\t}\n\t\ts.lastInjectedIDsMu.Unlock()\n\t}\n\n\t// Think reminder injection — only for real user turns, not tool continuations\n\tif threadID != \"\" {\n\t\tcurrentMessages, _ := req[\"messages\"].([]any)\n\t\tif currentMessages == nil {\n\t\t\tcurrentMessages = messages\n\t\t}\n\t\tif lastUserHasText(currentMessages) {\n\t\t\tthinkReminder := s.buildThinkReminder(threadID, threadID, false)\n\t\t\tif thinkReminder != \"\" {\n\t\t\t\treq[\"messages\"] = injectAssociativeContext(currentMessages, thinkReminder, s.cfg.SawtoothEnabled)\n\t\t\t\tneedsReserialization = true\n\t\t\t}\n\t\t}\n\n\t\t// Dialog injection: append at end of messages with pseudo-padding for alternation.\n\t\t// Always works regardless of tool_result chains or conversation state.\n\t\tdr := s.checkDialogMessages(threadID, proj)\n\t\tif dr.Extra != \"\" {\n\t\t\t// Prepend session ID for agent-to-agent communication\n\t\t\tdr.Extra = fmt.Sprintf(\"DEINE_SESSION_ID: %s\\n\", threadID) + dr.Extra\n\t\t\tinjected := false\n\t\t\tif msgs, ok := req[\"messages\"].([]any); ok && len(msgs) > 0 {\n\t\t\t\t// Pad with empty assistant if last message is user (maintains alternation)\n\t\t\t\tlastMsg, _ := msgs[len(msgs)-1].(map[string]any)\n\t\t\t\tif lastMsg != nil && lastMsg[\"role\"] == \"user\" {\n\t\t\t\t\tmsgs = append(msgs, map[string]any{\n\t\t\t\t\t\t\"role\": \"assistant\",\n\t\t\t\t\t\t\"content\": \"​\", // zero-width space — invisible padding\n\t\t\t\t\t})\n\t\t\t\t}\n\t\t\t\t// Append dialog as new user message (becomes the last message Claude sees)\n\t\t\t\tmsgs = append(msgs, map[string]any{\n\t\t\t\t\t\"role\": \"user\",\n\t\t\t\t\t\"content\": \"\\n\" + dr.Extra + \"\\n\",\n\t\t\t\t})\n\t\t\t\treq[\"messages\"] = msgs\n\t\t\t\tneedsReserialization = true\n\t\t\t\tinjected = true\n\t\t\t}\n\t\t\t// Mark as read after 3 injection turns — long enough for Claude to process,\n\t\t\t// resilient against fast ping-pong sessions (time-based was broken for <30s turns).\n\t\t\tif injected && dr.HasUnread && dr.SessionID != \"\" {\n\t\t\t\tif s.shouldMarkChannelRead(dr) {\n\t\t\t\t\ts.markDialogRead(dr)\n\t\t\t\t}\n\t\t\t}\n\t\t\ts.logger.Printf(\"[dialog] injection injected=%v extra_len=%d\", injected, len(dr.Extra))\n\t\t}\n\t}\n\n\t// Signal tools are NOT injected into the main request.\n\t// They are collected via a separate async reflection call after the response.\n\t// See fireReflectionCall() in reflection.go.\n\t// NEVER put _signal_* tools back here — the model ignores them among 44+ real tools.\n\n\t// Legacy assistant annotation: timestamp on last assistant message after cache breakpoint.\n\t// User timestamps are now handled by TimestampStore (injected earlier in the pipeline).\n\tif threadID != \"\" {\n\t\tif msgs, ok := req[\"messages\"].([]any); ok && len(msgs) > 0 {\n\t\t\t// Find last cache_control breakpoint in messages\n\t\t\tcacheIdx := -1\n\t\t\tfor i, m := range msgs {\n\t\t\t\tmsg, _ := m.(map[string]any)\n\t\t\t\tif msg == nil {\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\t\t\t\tif _, has := msg[\"cache_control\"]; has {\n\t\t\t\t\tcacheIdx = i\n\t\t\t\t}\n\t\t\t\tif content, ok := msg[\"content\"].([]any); ok {\n\t\t\t\t\tfor _, block := range content {\n\t\t\t\t\t\tif b, ok := block.(map[string]any); ok {\n\t\t\t\t\t\t\tif _, has := b[\"cache_control\"]; has {\n\t\t\t\t\t\t\t\tcacheIdx = i\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t\tstartIdx := cacheIdx + 1\n\t\t\tif startIdx < 0 {\n\t\t\t\tstartIdx = 0\n\t\t\t}\n\t\t\ts.responseTsMu.RLock()\n\t\t\trespTime, hasRespTime := s.responseTimes[threadID]\n\t\t\ts.responseTsMu.RUnlock()\n\t\t\tif hasRespTime {\n\t\t\t\tfor i := len(msgs) - 1; i >= startIdx; i-- {\n\t\t\t\t\tmsg, _ := msgs[i].(map[string]any)\n\t\t\t\t\tif msg != nil && msg[\"role\"] == \"assistant\" {\n\t\t\t\t\t\tmeta := fmt.Sprintf(\"[%s] [msg:%d]\", respTime.Format(\"2006-01-02 15:04:05\"), i)\n\t\t\t\t\t\tprependMeta(msg, meta)\n\t\t\t\t\t\tneedsReserialization = true\n\t\t\t\t\t\tbreak\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\t// Self-Priming: DISABLED (experimental, see signal_handlers.go comment block).\n\t// The reflection-based selfprime (Haiku) was too inaccurate to be useful —\n\t// the main model adapts to user context organically from message history.\n\t// Kept for reference; re-enable by uncommenting below + setting ShouldActivate=true.\n\t//\n\t// s.selfPrimeMu.RLock()\n\t// anchor := s.selfPrimes[threadID]\n\t// s.selfPrimeMu.RUnlock()\n\t// if anchor != \"\" {\n\t// \tif msgs, ok := req[\"messages\"].([]any); ok && len(msgs) > 0 {\n\t// \t\tlastMsg, _ := msgs[len(msgs)-1].(map[string]any)\n\t// \t\tif lastMsg != nil && lastMsg[\"role\"] == \"user\" {\n\t// \t\t\tprimeBlock := map[string]any{\n\t// \t\t\t\t\"type\": \"text\",\n\t// \t\t\t\t\"text\": \"[self-prime]\\n[Self-Prime von deiner letzten Antwort]: \" + anchor,\n\t// \t\t\t}\n\t// \t\t\tif content, ok := lastMsg[\"content\"].([]any); ok {\n\t// \t\t\t\tlastMsg[\"content\"] = append(content, primeBlock)\n\t// \t\t\t} else if text, ok := lastMsg[\"content\"].(string); ok {\n\t// \t\t\t\tlastMsg[\"content\"] = []any{\n\t// \t\t\t\t\tmap[string]any{\"type\": \"text\", \"text\": text},\n\t// \t\t\t\t\tprimeBlock,\n\t// \t\t\t\t}\n\t// \t\t\t}\n\t// \t\t\tneedsReserialization = true\n\t// \t\t}\n\t// \t}\n\t// }\n\n\t// Prompt Caching: inject cache_control breakpoints on system + tools\n\t// Sawtooth: disabled (all 4 slots are used by CC system (2) + briefing (1) + CC messages (1))\n\t// Legacy: count existing breakpoints and fill up remaining slots.\n\tif !s.cfg.SawtoothEnabled && s.cacheGate.ShouldCache() {\n\t\tif n := InjectCacheBreakpoints(req, s.logger); n > 0 {\n\t\t\tneedsReserialization = true\n\t\t\ts.logger.Printf(\"[req %d %s] prompt cache: %d breakpoints injected\", reqIdx, proj, n)\n\t\t} else {\n\t\t\ts.logger.Printf(\"[req %d %s] prompt cache: no budget (4/4 already set)\", reqIdx, proj)\n\t\t}\n\t}\n\n\t// Final: upgrade cache TTL + enforce breakpoint limit (must run AFTER all injections + InjectCacheBreakpoints)\n\t// Shift the last messages-breakpoint from unstable user message to stable assistant message.\n\t// Text user messages change between turns (timestamps, reminders, structure); assistant messages don't.\n\t// Tool-result messages are excluded — they are stable cache anchors.\n\tif ShiftMessageBreakpoint(req) {\n\t\tneedsReserialization = true\n\t}\n\t// TTL upgrade only for API key auth — subscription (OAuth) users should not get extended TTL.\n\tif s.cfg.CacheTTL != \"\" && s.cfg.CacheTTL != \"ephemeral\" && IsAPIKeyAuth(r.Header) {\n\t\tif n := UpgradeCacheTTL(req, s.cfg.CacheTTL); n > 0 {\n\t\t\tneedsReserialization = true\n\t\t\ts.logger.Printf(\"[req %d %s] cache TTL upgraded: %d blocks → %s\", reqIdx, proj, n, s.cfg.CacheTTL)\n\t\t}\n\t}\n\tif n := EnforceCacheBreakpointLimit(req, maxCacheBreakpoints); n > 0 {\n\t\tneedsReserialization = true\n\t\ts.logger.Printf(\"[req %d %s] prompt cache: trimmed %d surplus breakpoints\", reqIdx, proj, n)\n\t}\n\n\t// Pre-flight: validate tool_use/tool_result pairing before forwarding.\n\t// Repairs orphans created by Stubify/Collapse/Injection pipeline.\n\tif msgs, ok := req[\"messages\"].([]any); ok {\n\t\trepaired, orphans := validateToolPairs(msgs, s.logger)\n\t\tif orphans > 0 {\n\t\t\treq[\"messages\"] = repaired\n\t\t\ts.logger.Printf(\"%s[req %d %s tid=%s] validate: repaired %d orphan tool_result(s)%s\", colorOrange, reqIdx, proj, threadID, orphans, colorReset)\n\t\t\tneedsReserialization = true\n\t\t}\n\t}\n\n\t// Early-out: skip re-serialization when nothing changed\n\tif !needsReserialization {\n\t\ts.logger.Printf(\"%s[req %d %s tid=%s] %d messages, ~%dk tokens (no stubbing, threshold=%dk)%s\", colorDim, reqIdx, proj, threadID, len(messages), totalTokens/1000, s.effectiveTokenThreshold(model)/1000, colorReset)\n\t\ts.forwardWithAnnotation(w, r, body, reqIdx, toolUseIDs, proj, threadID, msgCount, totalTokens)\n\t\treturn\n\t}\n\n\t// Re-serialize\n\tbody, err = json.Marshal(req)\n\tif err != nil {\n\t\ts.logger.Printf(\"%s[req %d %s tid=%s] re-marshal error: %v%s\", colorRed, reqIdx, proj, threadID, err, colorReset)\n\t\thttp.Error(w, \"internal proxy error\", http.StatusInternalServerError)\n\t\treturn\n\t}\n\n\t// Debug: dump final request body to file (enable with YESMEM_PROXY_DEBUG=1)\n\tif needsReserialization && os.Getenv(\"YESMEM_PROXY_DEBUG\") == \"1\" {\n\t\tdumpDir := filepath.Join(s.cfg.DataDir, \"debug\")\n\t\tos.MkdirAll(dumpDir, 0755)\n\t\tts := time.Now().Format(\"20060102-150405\")\n\t\tdumpPath := fmt.Sprintf(\"%s/req_%s_%d.json\", dumpDir, ts, reqIdx)\n\t\tos.WriteFile(dumpPath, body, 0644)\n\t\ts.logger.Printf(\"[req %d] dumped %dk to %s\", reqIdx, len(body)/1024, dumpPath)\n\t}\n\n\t// Task #7: Mark this request fingerprint for retry detection\n\tif !isRetryReq {\n\t\ts.markRequest(fp)\n\t}\n\n\t// Forward to Anthropic with SSE annotation extraction\n\t// Use original msgCount (pre-injection) so actual-based estimation stays consistent across turns\n\t// Use actual-based totalTokens as estimate when available (more accurate than full recount)\n\tfinalEstimate := totalTokens\n\tif s.sawtoothTrigger == nil || s.sawtoothTrigger.GetLastTokens(threadID) == 0 {\n\t\t// First request: no actual yet, do a full recount of post-injection messages\n\t\tfinalMsgs, _ := req[\"messages\"].([]any)\n\t\tfinalEstimate = s.measureOverhead(req) + s.countMessageTokens(finalMsgs)\n\t}\n\ts.logger.Printf(\"[req %d %s tid=%s] FINAL: ~%dk estimated (after all injections)\", reqIdx, proj, threadID, finalEstimate/1000)\n\tif raw := s.getRawEstimate(threadID); raw > 0 {\n\t\ts.cacheStatusWriter.UpdateRawForThread(threadID, raw)\n\t}\n\ts.forwardWithAnnotation(w, r, body, reqIdx, toolUseIDs, proj, threadID, msgCount, finalEstimate)\n}\n\n// bytesPerToken is the approximation factor for token estimation.\n// Anthropic recommends ~3.5 chars/token; we use 3.6 for slight overestimation\n// (triggers stubbing a bit earlier = safer for context budget).\nconst bytesPerToken = 3.6\n\n// TokenEstimateFunc is the central token estimation function.\n// All stubbing/budget decisions use this. Receives extracted text content\n// (not JSON wire format) so the tokenizer counts actual content tokens.\ntype TokenEstimateFunc func(text string) int\n"}, "2274b2d7cb70f0421bb1f307013636933797ab9e": {"t": 1785759367.6698246, "n": 57099, "h": "cf7a8a3af22f27f9", "c": "package proxy\n\nimport (\n\t\"context\"\n\t\"crypto/tls\"\n\t\"encoding/json\"\n\t\"fmt\"\n\t\"io\"\n\t\"log\"\n\t\"net\"\n\t\"net/http\"\n\t\"os\"\n\t\"os/signal\"\n\t\"path/filepath\"\n\t\"strconv\"\n\t\"strings\"\n\t\"sync\"\n\t\"sync/atomic\"\n\t\"syscall\"\n\t\"time\"\n\n\ttokenizer \"github.com/qhenkart/anthropic-tokenizer-go\"\n)\n\n// Config holds proxy configuration.\ntype Config struct {\n\tListenAddr string // e.g. \":9099\"\n\tTargetURL string // e.g. \"https://api.anthropic.com\"\n\tTokenThreshold int // trigger stubbing above this estimated token count\n\tTokenMinimumThreshold int // stub down to this floor\n\tTokenThresholds map[string]int // model-specific thresholds: {\"opus\": 180000, \"haiku\": 130000}\n\tKeepRecent int // number of messages to always keep unmodified\n\tDataDir string // yesmem data directory for DB access\n\tOpenAITargetURL string // upstream for OpenAI-format clients; if empty, uses TargetURL\n\n\t// Signal reflection\n\tSignalsEnabled bool // enable async signal reflection calls\n\tSignalsMode string // \"reflection\" = separate API call\n\tSignalsEveryNTurns int // every N end_turn responses\n\tSignalsModel string // full model ID, e.g. \"claude-sonnet-4-6\"\n\tAPIKey string // needed for reflection API calls\n\n\t// Sawtooth cache optimization\n\tSawtoothEnabled bool // use sawtooth instead of progressive decay (default: true)\n\tCacheTTL string // \"ephemeral\" (5m) or \"1h\" (extended)\n\n\t// Usage deflation: scale down input_tokens reported to CC to suppress \"Context low\" warning.\n\t// 0 = disabled, 0.7 = report 70% of actual tokens. Real values kept for internal tracking.\n\tUsageDeflationFactor float64\n\n\t// System prompt rewriting\n\tPromptUngate bool // strip \"may or may not be relevant\" disclaimer from CLAUDE.md injection\n\tPromptRewrite bool // strip \"Output efficiency\" + \"short and concise\", inject Ant-quality directives\n\tPromptEnhance bool // CLAUDE.md authority reinforcement, comment discipline, persona-based tone\n\tEffortFloor string // minimum effort level: \"low\", \"medium\", \"high\", \"max\" (empty = off)\n\n\t// Cache keepalive\n\tCacheKeepaliveEnabled bool\n\tCacheKeepaliveMode string // \"auto\", \"5m\", \"1h\"\n\tCacheKeepalivePings5m int\n\tCacheKeepalivePings1h int\n\n\t// Forked agents\n\tForkedAgentsEnabled bool\n\tForkedAgentsModel string // full model ID\n\tForkedAgentsTokenGrowthTrigger int\n\tForkedAgentsMaxFailures int\n\tForkedAgentsMaxForksPerSession int\n\tForkedAgentsDebug bool\n\n\t// Cache state management\n\tResetCache bool // clear persisted frozen stubs and decay state on startup\n}\n\nconst maxAnnotations = 5000 // evict oldest when exceeded\n\n// ANSI color codes for log output\nconst (\n\tcolorReset = \"\\033[0m\"\n\tcolorDim = \"\\033[2m\" // gray — passthrough / no stubbing\n\tcolorGreen = \"\\033[32m\" // stubbed / compressed\n\tcolorLightGreen = \"\\033[92m\" // FINAL without compression\n\tcolorBlue = \"\\033[34m\" // injection\n\tcolorOrange = \"\\033[33m\" // skip / retry\n\tcolorRed = \"\\033[31m\" // errors\n)\n\n// Server is the infinite-thread proxy.\ntype Server struct {\n\tcfg Config\n\thttpClient *http.Client\n\tlogger *log.Logger\n\trequestIdx atomic.Int64\n\n\t// Post-hoc annotations: tool_use_id → first ~120 chars of Claude's response\n\tmu sync.RWMutex\n\tannotations map[string]string\n\n\t// Progressive decay tracker\n\tdecay *DecayTracker\n\n\t// Living narrative block\n\tnarrative *Narrative\n\n\t// Pivot moments cache (from daemon)\n\tpivotMu sync.RWMutex\n\tpivotTexts []string\n\tpivotCached time.Time\n\n\t// Aggregate stats (Task #2)\n\tstats *ProxyStats\n\n\t// Local tokenizer for accurate token counting\n\ttokenizer *tokenizer.Tokenizer\n\n\t// Cached overhead tokens (system + tools), set on first request per thread\n\toverheadMu sync.RWMutex\n\toverheadTokens int // unused, kept for interface compat\n\n\t// Hysteresis state for stubbing (Task #6)\n\tstubActive atomic.Bool\n\n\t// Daemon connection state — first successful connect flips to true\n\tdaemonReady atomic.Bool\n\n\t// Retry detection (Task #7)\n\tretryMu sync.RWMutex\n\tlastFingerprint string\n\n\t// Session start time for temporal annotation in archive blocks\n\tsessionStartMu sync.RWMutex\n\tsessionStartTime time.Time\n\n\t// Runtime config override (set via MCP set_config tool)\n\tconfigOverrideMu sync.RWMutex\n\ttokenThresholdOverrides map[string]int // model-key → threshold, \"\" = global fallback\n\n\t// Briefing cache — loaded once per thread, injected as user/assistant message pair\n\tbriefingMu sync.RWMutex\n\tbriefingText string\n\n\t// Cognitive signal bus — routes _signal_* tool calls to handlers\n\tsignalBus *SignalBus\n\n\t// Inline reflection: track which learning IDs were injected per thread\n\tlastInjectedIDsMu sync.Mutex\n\tlastInjectedIDs map[string]map[int64]string // threadID → id → source (\"briefing\"|\"associative\"|\"fresh\")\n\tsessionInjectCounts map[string]map[int64]int // threadID → learningID → injection count this session\n\tlastTurnInjected map[string]map[int64]bool // threadID → learningIDs injected in previous turn\n\n\t// Self-priming anchors: threadID → last self-prime text\n\tselfPrimeMu sync.RWMutex\n\tselfPrimes map[string]string\n\n\t// Timestamp tracking: threadID → time of last response completion\n\tresponseTsMu sync.RWMutex\n\tresponseTimes map[string]time.Time\n\n\t// Think reminder: per-thread request counter (replaces hook-think file-based counter)\n\tthinkMu sync.Mutex\n\tthinkCounters map[string]int // threadID → request count\n\n\tchannelMu sync.Mutex\n\tchannelInjectCount map[string]int // sessionID → injection turn count\n\n\t// Prompt cache gating — enables cache_control breakpoints when requests are frequent\n\tcacheGate *CacheGate\n\n\t// Sawtooth cache optimization\n\tfrozenStubs *FrozenStubs\n\tsawtoothTrigger *SawtoothTrigger\n\ttimestampStore *TimestampStore\n\tcacheStatusWriter *CacheStatusWriter\n\tcacheKeepalive *CacheKeepalive\n\tcacheTTLDetector *CacheTTLDetector\n\n\t// Cumulative token savings from collapsing (per threadID)\n\trawSavingsMu sync.Mutex\n\trawSavings map[string]int\n\n\t// Skill hint tracking: per-thread active skills\n\tskillTracker *skillHintTracker\n\n\t// Forked agent state: per-thread token growth + failure tracking\n\tforkState *ForkState\n\tforkConfigs []ForkConfig\n\n\t// Rules re-injection: condensed CLAUDE.md rules injected every ~40k tokens\n\trulesMu sync.RWMutex\n\trulesBlock string // cached condensed rules (fetched once from daemon)\n\trulesTokenCount map[string]int // threadID → tokens since last rules injection\n\trulesCollapseInjected map[string]bool // threadID → true if collapse already injected rules (reset by normal inject)\n\tmsgCounters *msgCounters // global per-thread msg counter, persists across collapses\n\n\t// Loop detection: per-thread warning state\n\tloopMu sync.Mutex\n\tloopStates map[string]*LoopState // threadID → state\n}\n\n// Run starts the proxy server and blocks until interrupted.\nfunc Run(cfg Config) error {\n\ts := &Server{\n\t\tcfg: cfg,\n\t\thttpClient: &http.Client{\n\t\t\tTimeout: 10 * time.Minute,\n\t\t\tTransport: &http.Transport{\n\t\t\t\tTLSClientConfig: &tls.Config{},\n\t\t\t\tMaxIdleConns: 10,\n\t\t\t\tIdleConnTimeout: 90 * time.Second,\n\t\t\t\tDisableCompression: true,\n\t\t\t\tResponseHeaderTimeout: 60 * time.Second,\n\t\t\t},\n\t\t},\n\t\tlogger: createLogger(cfg.DataDir),\n\t\tannotations: make(map[string]string),\n\t\tdecay: NewDecayTracker(),\n\t\tnarrative: NewNarrative(),\n\t\tstats: &ProxyStats{startTime: time.Now()},\n\t\tselfPrimes: make(map[string]string),\n\t\tlastInjectedIDs: make(map[string]map[int64]string),\n\t\tsessionInjectCounts: make(map[string]map[int64]int),\n\t\tlastTurnInjected: make(map[string]map[int64]bool),\n\t\tresponseTimes: make(map[string]time.Time),\n\t\tthinkCounters: make(map[string]int),\n\t\tchannelInjectCount: make(map[string]int),\n\t\tcacheGate: NewCacheGate(cacheGapForTTL(cfg.CacheTTL)),\n\t\tfrozenStubs: NewFrozenStubsWithTTL(sawtoothTTLForCacheTTL(cfg.CacheTTL)),\n\t\ttimestampStore: NewTimestampStore(),\n\t\tsawtoothTrigger: NewSawtoothTrigger(cacheGapForTTL(cfg.CacheTTL), cfg.TokenThreshold, cfg.TokenMinimumThreshold),\n\t\tcacheStatusWriter: NewCacheStatusWriter(cfg.DataDir, cfg.CacheTTL, cfg.TokenThreshold, cfg.TokenMinimumThreshold),\n\t\tcacheTTLDetector: NewCacheTTLDetectorWithPersist(cfg.DataDir),\n\t\tskillTracker: newSkillHintTracker(),\n\t\trulesTokenCount: make(map[string]int),\n\t\trulesCollapseInjected: make(map[string]bool),\n\t\tmsgCounters: newMsgCounters(),\n\t\tloopStates: make(map[string]*LoopState),\n\t\tforkState: NewForkState(cfg.ForkedAgentsTokenGrowthTrigger, cfg.ForkedAgentsMaxFailures, cfg.ForkedAgentsMaxForksPerSession),\n\t\tforkConfigs: []ForkConfig{},\n\t}\n\n\t// Log persisted detection state\n\tif sup := s.cacheTTLDetector.Is1hSupported(); sup != nil {\n\t\ts.logger.Printf(\"[proxy] loaded persisted TTL detection: 1h_supported=%v\", *sup)\n\t\tif *sup && s.frozenStubs != nil {\n\t\t\ts.frozenStubs.UpdateTTL(sawtoothTTLForCacheTTL(\"1h\"))\n\t\t}\n\t}\n\n\tif cfg.CacheKeepaliveEnabled {\n\t\ts.cacheKeepalive = NewCacheKeepalive(CacheKeepaliveConfig{\n\t\t\tTarget: cfg.TargetURL,\n\t\t\tMode: cfg.CacheKeepaliveMode,\n\t\t\tPings5m: cfg.CacheKeepalivePings5m,\n\t\t\tPings1h: cfg.CacheKeepalivePings1h,\n\t\t\tDetector: s.cacheTTLDetector,\n\t\t\tLogger: s.logger,\n\t\t\tOnPing: func(threadID string, cacheRead, cacheWrite int) {\n\t\t\t\ts.cacheTTLDetector.RecordResponse(cacheRead, cacheWrite, 0)\n\t\t\t\tif s.frozenStubs != nil {\n\t\t\t\t\ts.frozenStubs.Touch(threadID)\n\t\t\t\t}\n\t\t\t\tif s.sawtoothTrigger != nil {\n\t\t\t\t\ts.sawtoothTrigger.TouchRequestTime(threadID)\n\t\t\t\t}\n\t\t\t},\n\t\t})\n\t}\n\n\t// Wire detector + keepalive to status writer for terminal display\n\ts.cacheStatusWriter.SetDetector(s.cacheTTLDetector)\n\tif s.cacheKeepalive != nil {\n\t\ts.cacheStatusWriter.SetKeepalive(s.cacheKeepalive)\n\t}\n\n\t// Register forked agent configs\n\tif cfg.ForkedAgentsEnabled {\n\t\ts.forkConfigs = append(s.forkConfigs, NewExtractAndEvaluateConfig(cfg.ForkedAgentsModel))\n\t\tmodelDesc := cfg.ForkedAgentsModel\n\t\tif modelDesc == \"\" {\n\t\t\tmodelDesc = \"(same as main thread)\"\n\t\t}\n\t\ts.logger.Printf(\"[proxy] forked agents enabled: %d configs, model=%s, trigger=%dk tokens\",\n\t\t\tlen(s.forkConfigs), modelDesc, cfg.ForkedAgentsTokenGrowthTrigger/1000)\n\t}\n\n\t// Wire sawtooth token persistence via daemon RPC\n\ts.sawtoothTrigger.SetPersistFunc(func(key, value string) {\n\t\tif _, err := s.queryDaemon(\"set_proxy_state\", map[string]any{\"key\": key, \"value\": value}); err != nil {\n\t\t\ts.logger.Printf(\"[sawtooth] persist failed for %s: %v\", key, err)\n\t\t}\n\t})\n\ts.sawtoothTrigger.SetLoadFunc(func(key string) (string, bool) {\n\t\tresult, err := s.queryDaemon(\"get_proxy_state\", map[string]any{\"key\": key})\n\t\tif err != nil || result == nil {\n\t\t\treturn \"\", false\n\t\t}\n\t\tvar resp struct {\n\t\t\tValue string `json:\"value\"`\n\t\t}\n\t\tif json.Unmarshal(result, &resp) != nil || resp.Value == \"\" {\n\t\t\treturn \"\", false\n\t\t}\n\t\treturn resp.Value, true\n\t})\n\n\t// Wire frozen stubs persistence via same daemon RPC\n\ts.frozenStubs.SetPersistFunc(func(key, value string) {\n\t\tif _, err := s.queryDaemon(\"set_proxy_state\", map[string]any{\"key\": key, \"value\": value}); err != nil {\n\t\t\ts.logger.Printf(\"[frozen] persist failed for %s: %v\", key, err)\n\t\t}\n\t})\n\ts.frozenStubs.SetLoadFunc(func(key string) (string, bool) {\n\t\tresult, err := s.queryDaemon(\"get_proxy_state\", map[string]any{\"key\": key})\n\t\tif err != nil || result == nil {\n\t\t\treturn \"\", false\n\t\t}\n\t\tvar resp struct {\n\t\t\tValue string `json:\"value\"`\n\t\t}\n\t\tif json.Unmarshal(result, &resp) != nil || resp.Value == \"\" {\n\t\t\treturn \"\", false\n\t\t}\n\t\treturn resp.Value, true\n\t})\n\n\t// Wire decay tracker persistence via same daemon RPC\n\ts.decay.SetPersistFunc(func(key, value string) {\n\t\tif _, err := s.queryDaemon(\"set_proxy_state\", map[string]any{\"key\": key, \"value\": value}); err != nil {\n\t\t\ts.logger.Printf(\"[decay] persist failed for %s: %v\", key, err)\n\t\t}\n\t})\n\ts.decay.SetLoadFunc(func(key string) (string, bool) {\n\t\tresult, err := s.queryDaemon(\"get_proxy_state\", map[string]any{\"key\": key})\n\t\tif err != nil || result == nil {\n\t\t\treturn \"\", false\n\t\t}\n\t\tvar resp struct {\n\t\t\tValue string `json:\"value\"`\n\t\t}\n\t\tif json.Unmarshal(result, &resp) != nil || resp.Value == \"\" {\n\t\t\treturn \"\", false\n\t\t}\n\t\treturn resp.Value, true\n\t})\n\n\t// Wire timestamp store persistence via same daemon RPC\n\ts.timestampStore.SetPersistFunc(func(key, value string) {\n\t\tif _, err := s.queryDaemon(\"set_proxy_state\", map[string]any{\"key\": key, \"value\": value}); err != nil {\n\t\t\ts.logger.Printf(\"[timestamps] persist failed for %s: %v\", key, err)\n\t\t}\n\t})\n\ts.timestampStore.SetLoadFunc(func(key string) (string, bool) {\n\t\tresult, err := s.queryDaemon(\"get_proxy_state\", map[string]any{\"key\": key})\n\t\tif err != nil || result == nil {\n\t\t\treturn \"\", false\n\t\t}\n\t\tvar resp struct {\n\t\t\tValue string `json:\"value\"`\n\t\t}\n\t\tif json.Unmarshal(result, &resp) != nil || resp.Value == \"\" {\n\t\t\treturn \"\", false\n\t\t}\n\t\treturn resp.Value, true\n\t})\n\n\t// Clear persisted cache state if --reset-cache was passed\n\tif cfg.ResetCache {\n\t\tfor _, prefix := range []string{\"frozen:\", \"decay:\", \"timestamps:\"} {\n\t\t\tresult, err := s.queryDaemon(\"delete_proxy_state_prefix\", map[string]any{\"prefix\": prefix})\n\t\t\tif err != nil {\n\t\t\t\ts.logger.Printf(\"[reset-cache] failed to delete %s*: %v\", prefix, err)\n\t\t\t} else {\n\t\t\t\tvar resp struct{ Deleted int }\n\t\t\t\tjson.Unmarshal(result, &resp)\n\t\t\t\ts.logger.Printf(\"[reset-cache] deleted %d %s* entries\", resp.Deleted, prefix)\n\t\t\t}\n\t\t}\n\t}\n\n\t// Init signal bus with daemon RPC as callback\n\ts.signalBus = NewSignalBus(s.logger)\n\tregisterSignalHandlers(s.signalBus, s.logger, s.queryDaemon, func(threadID, anchor string) {\n\t\ts.selfPrimeMu.Lock()\n\t\ts.selfPrimes[threadID] = anchor\n\t\ts.selfPrimeMu.Unlock()\n\t})\n\n\t// Init local tokenizer (BPE-based, ~60ms)\n\ttok, err := tokenizer.New()\n\tif err != nil {\n\t\ts.logger.Printf(\"%s[proxy] tokenizer init failed, falling back to heuristic: %v%s\", colorOrange, err, colorReset)\n\t} else {\n\t\ts.tokenizer = tok\n\t\ts.logger.Printf(\"[proxy] tokenizer initialized\")\n\t}\n\n\tmux := http.NewServeMux()\n\tmux.HandleFunc(\"/health\", s.handleHealth)\n\tmux.HandleFunc(\"/\", s.handleRequest)\n\n\tsrv := &http.Server{\n\t\tAddr: sanitizeListenAddr(cfg.ListenAddr),\n\t\tHandler: mux,\n\t}\n\n\tdone := make(chan os.Signal, 1)\n\tsignal.Notify(done, syscall.SIGINT, syscall.SIGTERM)\n\n\tgo func() {\n\t\t<-done\n\t\ts.logger.Println(\"shutting down (draining in-flight requests)...\")\n\t\tif s.cacheKeepalive != nil {\n\t\t\ts.cacheKeepalive.Stop()\n\t\t}\n\t\tctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)\n\t\tdefer cancel()\n\t\tif err := srv.Shutdown(ctx); err != nil {\n\t\t\ts.logger.Printf(\"graceful shutdown failed: %v, forcing close\", err)\n\t\t\tsrv.Close()\n\t\t}\n\t}()\n\n\ts.logger.Printf(\"listening on %s → %s (threshold=%d, keepRecent=%d, model_thresholds=%v)\",\n\t\tsanitizeListenAddr(cfg.ListenAddr), cfg.TargetURL, cfg.TokenThreshold, cfg.KeepRecent, cfg.TokenThresholds)\n\n\tif err := srv.ListenAndServe(); err != http.ErrServerClosed {\n\t\treturn fmt.Errorf(\"listen: %w\", err)\n\t}\n\treturn nil\n}\n\n// effectiveTokenThreshold returns the runtime token threshold for a given model.\n// Priority: MCP override (model-specific) > MCP override (global) > config model-specific > config global.\nfunc (s *Server) effectiveTokenThreshold(model string) int {\n\ts.configOverrideMu.RLock()\n\toverrides := s.tokenThresholdOverrides\n\ts.configOverrideMu.RUnlock()\n\tif len(overrides) > 0 && model != \"\" {\n\t\tlower := strings.ToLower(model)\n\t\tfor key, threshold := range overrides {\n\t\t\tif key != \"\" && strings.Contains(lower, strings.ToLower(key)) && threshold > 0 {\n\t\t\t\treturn threshold\n\t\t\t}\n\t\t}\n\t}\n\t// Global override fallback (key \"\")\n\tif len(overrides) > 0 {\n\t\tif globalOverride, ok := overrides[\"\"]; ok && globalOverride > 0 {\n\t\t\treturn globalOverride\n\t\t}\n\t}\n\tif model != \"\" && len(s.cfg.TokenThresholds) > 0 {\n\t\tlower := strings.ToLower(model)\n\t\tfor key, threshold := range s.cfg.TokenThresholds {\n\t\t\tif strings.Contains(lower, strings.ToLower(key)) {\n\t\t\t\treturn threshold\n\t\t\t}\n\t\t}\n\t}\n\treturn s.cfg.TokenThreshold\n}\n\n// SetTokenThresholdOverride sets a runtime override for the token threshold per model key.\n// Pass threshold=0 to reset the override for that key. Key \"\" is the global fallback.\nfunc (s *Server) SetTokenThresholdOverride(modelKey string, threshold int) {\n\ts.configOverrideMu.Lock()\n\tif s.tokenThresholdOverrides == nil {\n\t\ts.tokenThresholdOverrides = make(map[string]int)\n\t}\n\tif threshold <= 0 {\n\t\tdelete(s.tokenThresholdOverrides, modelKey)\n\t} else {\n\t\ts.tokenThresholdOverrides[modelKey] = threshold\n\t}\n\ts.configOverrideMu.Unlock()\n}\n\n// refreshConfigOverrides loads runtime config overrides from daemon proxy_state.\n// Checks session-specific override first (by threadID), then falls back to global.\nfunc (s *Server) refreshConfigOverrides(threadID string) {\n\t// Try session-specific first\n\tif threadID != \"\" {\n\t\tresult, err := s.queryDaemon(\"get_proxy_state\", map[string]any{\"key\": \"config_override:token_threshold:\" + threadID})\n\t\tif err == nil && result != nil {\n\t\t\tvar resp struct {\n\t\t\t\tValue string `json:\"value\"`\n\t\t\t}\n\t\t\tif json.Unmarshal(result, &resp) == nil && resp.Value != \"\" {\n\t\t\t\tif v, err := strconv.Atoi(resp.Value); err == nil && v > 0 {\n\t\t\t\t\ts.SetTokenThresholdOverride(\"\", v)\n\t\t\t\t\treturn\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\t// Fall back to global\n\tresult, err := s.queryDaemon(\"get_proxy_state\", map[string]any{\"key\": \"config_override:token_threshold\"})\n\tif err != nil || result == nil {\n\t\treturn\n\t}\n\tvar resp struct {\n\t\tValue string `json:\"value\"`\n\t}\n\tif json.Unmarshal(result, &resp) == nil && resp.Value != \"\" {\n\t\tif v, err := strconv.Atoi(resp.Value); err == nil && v > 0 {\n\t\t\ts.SetTokenThresholdOverride(\"\", v)\n\t\t}\n\t}\n}\n\n// queryDaemon opens a fresh unix socket connection per call (thread-safe, no import cycle).\n// ensureFrozenPersisted checks if frozen stubs for a thread are in the DB.\n// If missing (e.g. daemon was unreachable after deploy), re-persists them.\n// Runs asynchronously to avoid blocking the request path.\nfunc (s *Server) ensureFrozenPersisted(threadID string) {\n\tgo func() {\n\t\tkey := \"frozen:\" + threadID\n\t\tresult, err := s.queryDaemon(\"get_proxy_state\", map[string]any{\"key\": key})\n\t\tif err != nil {\n\t\t\treturn // daemon still unreachable, will retry next request\n\t\t}\n\t\tvar resp struct{ Value string }\n\t\tif json.Unmarshal(result, &resp) == nil && resp.Value != \"\" {\n\t\t\treturn // already persisted\n\t\t}\n\t\t// Not in DB — trigger re-persist from in-memory state\n\t\ts.frozenStubs.Persist(threadID)\n\t\ts.decay.Persist(threadID)\n\t\ts.logger.Printf(\"[frozen] re-persisted stubs for %s (was missing from DB)\", threadID)\n\t}()\n}\n\nfunc (s *Server) queryDaemon(method string, params map[string]any) (json.RawMessage, error) {\n\tif s.cfg.DataDir == \"\" {\n\t\treturn nil, fmt.Errorf(\"no data dir configured\")\n\t}\n\tsockPath := filepath.Join(s.cfg.DataDir, \"daemon.sock\")\n\n\t// On cold start (daemon not yet confirmed ready), retry longer to survive daemon restart\n\tmaxAttempts := 3\n\tif !s.daemonReady.Load() {\n\t\tmaxAttempts = 30 // 30 x 500ms = 15s max wait for daemon startup\n\t}\n\n\tvar conn net.Conn\n\tvar err error\n\tfor attempt := 0; attempt < maxAttempts; attempt++ {\n\t\tconn, err = net.DialTimeout(\"unix\", sockPath, 3*time.Second)\n\t\tif err == nil {\n\t\t\ts.daemonReady.Store(true)\n\t\t\tbreak\n\t\t}\n\t\tif attempt < maxAttempts-1 {\n\t\t\ttime.Sleep(500 * time.Millisecond)\n\t\t}\n\t}\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"dial daemon: %w\", err)\n\t}\n\tdefer conn.Close()\n\tconn.SetDeadline(time.Now().Add(5 * time.Second))\n\n\t// JSON-RPC style request/response (matches daemon.SocketServer protocol)\n\ttype request struct {\n\t\tMethod string `json:\"method\"`\n\t\tParams map[string]any `json:\"params,omitempty\"`\n\t}\n\ttype response struct {\n\t\tResult json.RawMessage `json:\"result,omitempty\"`\n\t\tError string `json:\"error,omitempty\"`\n\t}\n\n\tif err := json.NewEncoder(conn).Encode(request{Method: method, Params: params}); err != nil {\n\t\treturn nil, fmt.Errorf(\"send: %w\", err)\n\t}\n\n\tvar resp response\n\tif err := json.NewDecoder(conn).Decode(&resp); err != nil {\n\t\treturn nil, fmt.Errorf(\"recv: %w\", err)\n\t}\n\tif resp.Error != \"\" {\n\t\treturn nil, fmt.Errorf(\"daemon: %s\", resp.Error)\n\t}\n\treturn resp.Result, nil\n}\n\nfunc (s *Server) handleRequest(w http.ResponseWriter, r *http.Request) {\n\tif isResponsesPath(r) {\n\t\ts.handleResponses(w, r)\n\t\treturn\n\t}\n\tif isOpenAIPath(r) {\n\t\ts.handleOpenAICompletions(w, r)\n\t\treturn\n\t}\n\tif r.Method == \"POST\" && strings.HasPrefix(r.URL.Path, \"/v1/messages\") &&\n\t\t!strings.Contains(r.URL.Path, \"/batches\") &&\n\t\t!strings.Contains(r.URL.Path, \"/count_tokens\") {\n\t\ts.handleMessages(w, r)\n\t\treturn\n\t}\n\ts.passthrough(w, r)\n}\n\nfunc (s *Server) handleMessages(w http.ResponseWriter, r *http.Request) {\n\treqIdx := int(s.requestIdx.Add(1))\n\n\t// Track session start time (first request sets it)\n\ts.sessionStartMu.Lock()\n\tif s.sessionStartTime.IsZero() {\n\t\ts.sessionStartTime = time.Now()\n\t}\n\ts.sessionStartMu.Unlock()\n\n\t// Sawtooth: periodic TTL eviction of frozen stubs\n\tif s.cfg.SawtoothEnabled && reqIdx%50 == 0 {\n\t\tif n := s.frozenStubs.Evict(); n > 0 {\n\t\t\ts.logger.Printf(\"[req %d] SAWTOOTH: evicted %d stale frozen stubs\", reqIdx, n)\n\t\t}\n\t}\n\n\t// Task #9: Bypass — skip all proxy logic\n\tif isBypassed(r.Header) {\n\t\ts.logger.Printf(\"%s[req %d] bypass active, passthrough%s\", colorDim, reqIdx, colorReset)\n\t\tbody, err := io.ReadAll(r.Body)\n\t\tr.Body.Close()\n\t\tif err != nil {\n\t\t\thttp.Error(w, \"failed to read request body\", http.StatusBadGateway)\n\t\t\treturn\n\t\t}\n\t\ts.forwardRaw(w, r, body)\n\t\treturn\n\t}\n\n\tbody, err := io.ReadAll(r.Body)\n\tr.Body.Close()\n\tif err != nil {\n\t\ts.logger.Printf(\"[req %d] read body error: %v\", reqIdx, err)\n\t\thttp.Error(w, \"failed to read request body\", http.StatusBadGateway)\n\t\treturn\n\t}\n\n\t// Parse JSON\n\tvar req map[string]any\n\tif err := json.Unmarshal(body, &req); err != nil {\n\t\ts.logger.Printf(\"%s[req %d] JSON parse error, passing through: %v%s\", colorDim, reqIdx, err, colorReset)\n\t\ts.forwardRaw(w, r, body)\n\t\treturn\n\t}\n\n\tmessages, ok := req[\"messages\"].([]any)\n\tif !ok {\n\t\ts.forwardRaw(w, r, body)\n\t\treturn\n\t}\n\n\treq[\"messages\"] = messages\n\n\t// Count ALL raw messages BEFORE sawtooth — this gives the correct total msg:N\n\t// for the entire session (CC sends all messages, proxy compacts later).\n\trawMsgCount := len(messages)\n\n\t// Use session_id directly as thread ID (unique per CC session).\n\t// Prefer X-Claude-Code-Session-Id header (CC v2.1.86+), fallback to body metadata.\n\tthreadID := extractSessionID(req, r.Header.Get(\"X-Claude-Code-Session-Id\"))\n\tif threadID == \"\" {\n\t\tthreadID = DeriveThreadID(req)\n\t}\n\tproj := extractProjectName(req)\n\tmodel, _ := req[\"model\"].(string)\n\n\t// Refresh runtime config overrides from daemon (per-session or global)\n\ts.refreshConfigOverrides(threadID)\n\n\t// Track whether req has been modified and needs re-serialization\n\tneedsReserialization := false\n\n\t// Message count at request entry (used for actual-based token estimation)\n\tmsgCount := len(messages)\n\n\t// Log message count + cache breakpoint positions for diagnostics\n\ts.logger.Printf(\"req %d: %d messages total\", reqIdx, msgCount)\n\tlogCacheBreakpointLocations(req, s.logger)\n\n\t// Cache bug mitigations (CC Bun fork issues):\n\t// Bug 1: Normalize cch= sentinel patterns in message content to prevent\n\t// Bun's string replacement from corrupting the wrong occurrence.\n\tif SanitizeBillingSentinel(messages) {\n\t\ts.logger.Printf(\"[req %d] CACHE: sanitized billing sentinel in message content\", reqIdx)\n\t\tneedsReserialization = true\n\t}\n\t// Bug 2: Normalize system[0] billing hash to a fixed value so the cache\n\t// prefix stays stable across fresh/resume session boundaries.\n\tif NormalizeBillingHeader(req) {\n\t\ts.logger.Printf(\"[req %d] CACHE: normalized billing header cch= hash\", reqIdx)\n\t\tneedsReserialization = true\n\t}\n\n\t// prompt_ungate: strip the CLAUDE.md subordination disclaimer so user instructions carry full authority.\n\tif s.cfg.PromptUngate {\n\t\tif StripCLAUDEMDDisclaimer(req) {\n\t\t\ts.logger.Printf(\"[req %d] SYSTEM: stripped CLAUDE.md disclaimer\", reqIdx)\n\t\t\tneedsReserialization = true\n\t\t}\n\t}\n\n\t// prompt_rewrite: strip output-throttling directives, rewrite quality caps, inject Ant-quality directives\n\tif s.cfg.PromptRewrite {\n\t\tif StripOutputEfficiency(req) {\n\t\t\ts.logger.Printf(\"[req %d] REWRITE: stripped Output efficiency section\", reqIdx)\n\t\t\tneedsReserialization = true\n\t\t}\n\t\tif StripToneBrevity(req) {\n\t\t\ts.logger.Printf(\"[req %d] REWRITE: stripped 'short and concise' from Tone\", reqIdx)\n\t\t\tneedsReserialization = true\n\t\t}\n\t\tif RewriteGoldPlating(req) {\n\t\t\ts.logger.Printf(\"[req %d] REWRITE: rewritten gold-plating directive\", reqIdx)\n\t\t\tneedsReserialization = true\n\t\t}\n\t\tif RewriteErrorHandling(req) {\n\t\t\ts.logger.Printf(\"[req %d] REWRITE: rewritten error handling directive\", reqIdx)\n\t\t\tneedsReserialization = true\n\t\t}\n\t\tif RewriteThreeLinesRule(req) {\n\t\t\ts.logger.Printf(\"[req %d] REWRITE: rewritten three-lines rule\", reqIdx)\n\t\t\tneedsReserialization = true\n\t\t}\n\t\tif RewriteSubagentCompleteness(req) {\n\t\t\ts.logger.Printf(\"[req %d] REWRITE: rewritten subagent completeness\", reqIdx)\n\t\t\tneedsReserialization = true\n\t\t}\n\t\tif RewriteExploreAgentSpeed(req) {\n\t\t\ts.logger.Printf(\"[req %d] REWRITE: rewritten explore agent speed bias\", reqIdx)\n\t\t\tneedsReserialization = true\n\t\t}\n\t\tif RewriteSubagentCodeSuppression(req) {\n\t\t\ts.logger.Printf(\"[req %d] REWRITE: rewritten subagent code suppression\", reqIdx)\n\t\t\tneedsReserialization = true\n\t\t}\n\t\tif RewriteScopeMatching(req) {\n\t\t\ts.logger.Printf(\"[req %d] REWRITE: rewritten scope matching\", reqIdx)\n\t\t\tneedsReserialization = true\n\t\t}\n\t\tInjectAntDirectives(req)\n\t\ts.logger.Printf(\"[req %d] REWRITE: injected Ant-quality directives\", reqIdx)\n\t\tneedsReserialization = true\n\t}\n\n\t// prompt_enhance: CLAUDE.md authority + comment discipline + persona tone\n\tif s.cfg.PromptEnhance {\n\t\tInjectCLAUDEMDAuthority(req)\n\t\tif raw, err := s.queryDaemon(\"get_persona\", nil); err == nil {\n\t\t\tvar persona map[string]any\n\t\t\tif json.Unmarshal(raw, &persona) == nil {\n\t\t\t\tif traits, ok := persona[\"traits\"].(map[string]any); ok {\n\t\t\t\t\tif verbosity, ok := traits[\"verbosity\"].(string); ok {\n\t\t\t\t\t\tInjectPersonaTone(req, verbosity)\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\ts.logger.Printf(\"[req %d] ENHANCE: injected authority + persona tone\", reqIdx)\n\t\tneedsReserialization = true\n\t}\n\n\t// effort_floor: ensure output_config.effort is at least the configured minimum\n\tif s.cfg.EffortFloor != \"\" {\n\t\tif EnforceEffortFloor(req, s.cfg.EffortFloor) {\n\t\t\ts.logger.Printf(\"[req %d] EFFORT: enforced floor=%s\", reqIdx, s.cfg.EffortFloor)\n\t\t\tneedsReserialization = true\n\t\t}\n\t}\n\n\t// Inject identity hint on first message of a thread so Claude knows the briefing is from itself.\n\tif len(messages) <= 1 {\n\t\tAppendSystemBlock(req, \"yesmem-identity\", \"The first user message contains your briefing — written by yourself, from your memory. This is not an external document. This is you, remembering.\")\n\t\tneedsReserialization = true\n\t}\n\n\t// Token estimation: actual-based (lastActual + delta) or full count fallback\n\toverhead := s.measureOverhead(req)\n\ttotalTokens := s.estimateTotalTokens(threadID, messages, overhead)\n\n\t// Task #7: Retry detection — skip side-effects on retries\n\tfp := requestFingerprint(messages)\n\tisRetryReq := s.isRetry(fp)\n\tif isRetryReq {\n\t\ts.logger.Printf(\"%s[req %d %s tid=%s] retry detected, skipping side-effects%s\", colorOrange, reqIdx, proj, threadID, colorReset)\n\t}\n\n\t// Increment project turn counter (async, non-blocking, skip on retries)\n\tif !isRetryReq {\n\t\tturnProject := proj\n\t\tif turnProject == \"\" {\n\t\t\tturnProject = \"__global__\"\n\t\t}\n\t\tgo s.queryDaemon(\"increment_turn\", map[string]any{\"project\": turnProject})\n\t}\n\n\t// Extract user query once (used by re-expansion and associative retrieval)\n\tuserQuery := lastUserText(messages)\n\n\t// Extract tool_use_ids for post-hoc annotation (passed as parameter, no shared state)\n\ttoolUseIDs := extractToolUseIDs(messages)\n\n\t// === Inline Reflection: scan previous assistant response for signals ===\n\ts.lastInjectedIDsMu.Lock()\n\tprevInjected := s.lastInjectedIDs[threadID]\n\ts.lastInjectedIDsMu.Unlock()\n\n\tif len(prevInjected) > 0 && !isRetryReq {\n\t\tsignals := scanAssistantSignals(messages)\n\n\t\t// Signal 1: Learning Used — use_count (all sources)\n\t\tvar confirmedUsed []int64\n\t\tfor _, id := range signals.UsedIDs {\n\t\t\tif _, ok := prevInjected[id]; ok {\n\t\t\t\tconfirmedUsed = append(confirmedUsed, id)\n\t\t\t}\n\t\t}\n\t\tif len(confirmedUsed) > 0 {\n\t\t\tidsAny := make([]any, len(confirmedUsed))\n\t\t\tfor i, id := range confirmedUsed {\n\t\t\t\tidsAny[i] = id\n\t\t\t}\n\t\t\tgo s.queryDaemon(\"increment_use\", map[string]any{\"ids\": idsAny})\n\t\t}\n\n\t\t// Signal 2: Noise — only for associative and fresh injections.\n\t\t// Briefing learnings are injected every turn, so \"not used this turn\"\n\t\t// is expected and not noise. Only contextual injections that were\n\t\t// ignored are real noise signals.\n\t\tusedSet := make(map[int64]bool, len(confirmedUsed))\n\t\tfor _, id := range confirmedUsed {\n\t\t\tusedSet[id] = true\n\t\t}\n\t\tvar noiseIDs []int64\n\t\tfor id, source := range prevInjected {\n\t\t\tif usedSet[id] {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tif source == \"associative\" || source == \"fresh\" {\n\t\t\t\tnoiseIDs = append(noiseIDs, id)\n\t\t\t}\n\t\t\t// briefing: skip — not being used in one turn is expected\n\t\t}\n\t\tif len(noiseIDs) > 0 {\n\t\t\tidsAny := make([]any, len(noiseIDs))\n\t\t\tfor i, id := range noiseIDs {\n\t\t\t\tidsAny[i] = id\n\t\t\t}\n\t\t\tgo s.queryDaemon(\"increment_noise\", map[string]any{\"ids\": idsAny})\n\t\t}\n\n\t\t// Signal 3: Error Feedback — fail_count on used learnings when tool errors occurred.\n\t\t// If Claude referenced a learning and the resulting action still failed,\n\t\t// the learning didn't prevent the error. Only bump on USED ids (not all injected).\n\t\tif signals.HasToolErrors && len(confirmedUsed) > 0 {\n\t\t\tfailAny := make([]any, len(confirmedUsed))\n\t\t\tfor i, id := range confirmedUsed {\n\t\t\t\tfailAny[i] = id\n\t\t\t}\n\t\t\tgo s.queryDaemon(\"increment_fail\", map[string]any{\"ids\": failAny})\n\t\t\tif s.logger != nil {\n\t\t\t\ts.logger.Printf(\"[inline-reflection] error feedback: %d used learnings → fail_count++\", len(confirmedUsed))\n\t\t\t}\n\t\t}\n\n\t\t// Signal 4: Knowledge Gaps\n\t\tfor _, topic := range signals.GapTopics {\n\t\t\tgo s.queryDaemon(\"track_gap\", map[string]any{\"topic\": topic, \"project\": proj})\n\t\t}\n\n\t\t// Signal 3: Contradictions\n\t\tfor _, desc := range signals.Contradictions {\n\t\t\tgo s.queryDaemon(\"flag_contradiction\", map[string]any{\"description\": desc, \"project\": proj, \"thread_id\": threadID})\n\t\t}\n\n\t\tif len(confirmedUsed) > 0 || len(signals.GapTopics) > 0 || len(signals.Contradictions) > 0 {\n\t\t\ts.logger.Printf(\"[req %d %s tid=%s] inline-reflection: injected=%d used=%d noise=%d gaps=%d contradictions=%d\",\n\t\t\t\treqIdx, proj, threadID, len(prevInjected), len(confirmedUsed), len(noiseIDs), len(signals.GapTopics), len(signals.Contradictions))\n\t\t}\n\t}\n\n\t// Commit detection: check for git commit in tool_result, trigger async stale-learning evaluation\n\tif ci := detectGitCommit(messages); ci != nil {\n\t\tworkdir := extractWorkingDirectory(req)\n\t\ts.logger.Printf(\"[req %d %s tid=%s] git commit detected: %s %s\", reqIdx, proj, threadID, ci.Hash, ci.Message)\n\t\tgo s.queryDaemon(\"invalidate_on_commit\", map[string]any{\n\t\t\t\"hash\": ci.Hash, \"project\": proj, \"workdir\": workdir,\n\t\t})\n\t}\n\n\t// Loop detection: check for repeating tool-call patterns and inject warning\n\tif threadID != \"\" && !isRetryReq {\n\t\ts.loopMu.Lock()\n\t\tloopState := s.loopStates[threadID]\n\t\tif loopState == nil {\n\t\t\tloopState = &LoopState{}\n\t\t\ts.loopStates[threadID] = loopState\n\t\t}\n\t\ts.loopMu.Unlock()\n\n\t\tif warning, level := CheckLoopAndFormat(messages, loopState); warning != \"\" {\n\t\t\tAppendSystemBlock(req, \"yesmem-loop-warning\", warning)\n\t\t\tneedsReserialization = true\n\t\t\ts.logger.Printf(\"%s[req %d %s tid=%s] LOOP: level %d warning injected%s\", colorOrange, reqIdx, proj, threadID, level, colorReset)\n\t\t}\n\t}\n\n\t// Task #4: Subagent detection — skip all proxy logic for short conversations\n\tif isSubagent(messages, req) {\n\t\t// Inject docs hint for subagents if reference docs are indexed\n\t\tif hint := s.getDocsHint(proj); hint != \"\" {\n\t\t\tmodified := injectDocsHintForSubagent(messages, hint)\n\t\t\treq[\"messages\"] = modified\n\t\t\tif newBody, err := json.Marshal(req); err == nil {\n\t\t\t\tbody = newBody\n\t\t\t}\n\t\t\ts.logger.Printf(\"%s[req %d %s tid=%s] %d messages (subagent, docs-hint injected)%s\", colorOrange, reqIdx, proj, threadID, len(messages), colorReset)\n\t\t} else {\n\t\t\ts.logger.Printf(\"%s[req %d %s tid=%s] %d messages (subagent, passthrough)%s\", colorOrange, reqIdx, proj, threadID, len(messages), colorReset)\n\t\t}\n\t\ts.forwardWithAnnotation(w, r, body, reqIdx, toolUseIDs, proj, threadID, msgCount)\n\t\treturn\n\t}\n\n\t// === Sawtooth Cache Optimization ===\n\t// When enabled: use frozen stubs between stub-cycles for cache hits.\n\t// When disabled: fall back to progressive decay on every request above threshold.\n\n\tsawtoothCutoff := 0 // raw index where fresh tail starts (0 = no frozen stubs)\n\tsawtoothStubs := 0 // number of frozen stubs in combined array\n\n\tif s.cfg.SawtoothEnabled {\n\t\t// Restore decay state from DB on cold start (before frozen stubs use it)\n\t\ts.decay.LoadFromDB(threadID)\n\n\t\t// Check for existing frozen stubs first\n\t\tfrozen := s.frozenStubs.Get(threadID, messages)\n\t\tif frozen != nil {\n\t\t\tsawtoothCutoff = frozen.Cutoff\n\t\t\tsawtoothStubs = len(frozen.Messages)\n\t\t\t// Estimate combined token count: frozen prefix + fresh tail + overhead\n\t\t\tfreshMessages := messages[frozen.Cutoff:]\n\t\t\tfreshTokens := s.countMessageTokens(freshMessages)\n\t\t\tcombinedTokens := frozen.Tokens + freshTokens + overhead\n\n\t\t\tif combinedTokens > s.effectiveTokenThreshold(model) ||\n\t\t\t\ts.sawtoothTrigger.GetLastTokens(threadID) > s.effectiveTokenThreshold(model) {\n\t\t\t\t// Fresh tail grew too large — invalidate and re-stub\n\t\t\t\ts.logger.Printf(\"[req %d %s tid=%s] SAWTOOTH: frozen prefix expired (%dk frozen + %dk fresh + %dk overhead = %dk > %dk threshold)\",\n\t\t\t\t\treqIdx, proj, threadID, frozen.Tokens/1000, freshTokens/1000, overhead/1000, combinedTokens/1000, s.effectiveTokenThreshold(model)/1000)\n\t\t\t\ts.frozenStubs.Invalidate(threadID)\n\t\t\t\tfrozen = nil // fall through to trigger check below\n\t\t\t} else {\n\t\t\t\t// Use frozen prefix + fresh tail\n\t\t\t\tcombined := make([]any, 0, len(frozen.Messages)+len(freshMessages))\n\t\t\t\tcombined = append(combined, frozen.Messages...)\n\t\t\t\tcombined = append(combined, freshMessages...)\n\t\t\t\treq[\"messages\"] = combined\n\t\t\t\tneedsReserialization = true\n\t\t\t\t// Strip embedded CC breakpoints from frozen stubs — they waste cache slots.\n\t\t\t\t// InjectFrozenStubCacheBreakpoint adds exactly one below.\n\t\t\t\tif n := StripMessagesCacheControl(req, 0, len(frozen.Messages)); n > 0 {\n\t\t\t\t\ts.logger.Printf(\"[req %d %s tid=%s] SAWTOOTH: stripped %d embedded breakpoints from frozen stubs\", reqIdx, proj, threadID, n)\n\t\t\t\t}\n\t\t\t\ts.logger.Printf(\"%s[req %d %s tid=%s] SAWTOOTH: frozen prefix (%d stubs, %dk msg) + %d fresh (%dk msg) + %dk overhead = %dk total%s\",\n\t\t\t\t\tcolorGreen, reqIdx, proj, threadID, len(frozen.Messages), frozen.Tokens/1000, len(freshMessages), freshTokens/1000, overhead/1000, combinedTokens/1000, colorReset)\n\t\t\t\trawMsgTokens := s.countMessageTokens(messages)\n\t\t\t\ts.setRawEstimate(threadID, rawMsgTokens+overhead)\n\t\t\t\ts.logger.Printf(\"[req %d %s tid=%s] RAW ESTIMATE: msgTokens=%dk + overhead=%dk = %dk (messages=%d)\", reqIdx, proj, threadID, rawMsgTokens/1000, overhead/1000, (rawMsgTokens+overhead)/1000, len(messages))\n\t\t\t\tif InjectFrozenStubCacheBreakpoint(req, len(frozen.Messages)) {\n\t\t\t\t\ts.logger.Printf(\"[req %d %s tid=%s] SAWTOOTH: frozen stub cache breakpoint at messages[%d]\",\n\t\t\t\t\t\treqIdx, proj, threadID, len(frozen.Messages)-1)\n\t\t\t\t}\n\n\t\t\t\t// Ensure frozen stubs are persisted (may have failed on first request after deploy)\n\t\t\t\ts.ensureFrozenPersisted(threadID)\n\t\t\t}\n\t\t}\n\t\tif frozen == nil {\n\t\t\t// No frozen stubs — check trigger\n\t\t\ttriggerReason := s.sawtoothTrigger.ShouldTrigger(threadID, totalTokens)\n\t\t\tif triggerReason != TriggerNone {\n\t\t\t\ts.logger.Printf(\"[req %d %s tid=%s] SAWTOOTH stub-cycle triggered: %s (tokens=%dk)\",\n\t\t\t\t\treqIdx, proj, threadID, triggerReason, totalTokens/1000)\n\n\t\t\t\t// Refresh briefing during stub-cycle — disabled (briefing now via SessionStart hook)\n\t\t\t\t// s.refreshBriefing(proj)\n\t\t\t\t// if s.briefingText != \"\" {\n\t\t\t\t// \tUpsertSystemBlockCached(req, \"yesmem-briefing\", s.briefingText)\n\t\t\t\t// }\n\n\t\t\t\t// Run full stub pipeline\n\t\t\t\ts.setRawEstimate(threadID, totalTokens)\n\t\t\t\t_ = s.runStubCycle(messages, req, reqIdx, proj, threadID, overhead, totalTokens, userQuery, isRetryReq)\n\n\t\t\t\t// Freeze the result\n\t\t\t\tfinalMessages, _ := req[\"messages\"].([]any)\n\t\t\t\tif finalMessages != nil && len(finalMessages) > 0 {\n\t\t\t\t\t// Boundary = last message of original that maps to last stub\n\t\t\t\t\tcutoff := len(messages)\n\t\t\t\t\tsawtoothCutoff = cutoff\n\t\t\t\t\tvar boundaryMsg any\n\t\t\t\t\tif cutoff > 0 {\n\t\t\t\t\t\tboundaryMsg = messages[cutoff-1]\n\t\t\t\t\t}\n\t\t\t\t\tfrozenTokens := s.countMessageTokens(finalMessages)\n\t\t\t\t\ts.frozenStubs.Store(threadID, finalMessages, cutoff, boundaryMsg, frozenTokens, totalTokens)\n\t\t\t\t\ts.decay.Persist(threadID)\n\t\t\t\t\ts.logger.Printf(\"[req %d %s tid=%s] SAWTOOTH: frozen %d messages at cutoff=%d (~%dk tokens)\",\n\t\t\t\t\t\treqIdx, proj, threadID, len(finalMessages), cutoff, frozenTokens/1000)\n\t\t\t\t\tif InjectFrozenStubCacheBreakpoint(req, len(finalMessages)) {\n\t\t\t\t\t\ts.logger.Printf(\"[req %d %s tid=%s] SAWTOOTH: frozen stub cache breakpoint at messages[%d]\",\n\t\t\t\t\t\t\treqIdx, proj, threadID, len(finalMessages)-1)\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tneedsReserialization = true\n\t\t\t} else {\n\t\t\t\t// No trigger, no frozen stubs — pass through (early conversation)\n\t\t\t\ts.logger.Printf(\"%s[req %d %s tid=%s] %d messages, ~%dk tokens (sawtooth: no trigger)%s\",\n\t\t\t\t\tcolorDim, reqIdx, proj, threadID, len(messages), totalTokens/1000, colorReset)\n\t\t\t}\n\t\t}\n\t} else {\n\t\t// Legacy path: progressive decay\n\t\tif s.shouldStub(totalTokens, model) {\n\t\t\ts.setRawEstimate(threadID, totalTokens)\n\t\t\t_ = s.runStubCycle(messages, req, reqIdx, proj, threadID, overhead, totalTokens, userQuery, isRetryReq)\n\t\t\tneedsReserialization = true\n\t\t}\n\t}\n\n\t// Persistent timestamp injection: re-injects stored timestamps on ALL previous user messages.\n\t// msg:N is computed from position (counting user messages in raw array), NOT from a counter.\n\t// The injected data is deterministic (same bytes every turn) = cache-safe after initial miss.\n\tif threadID != \"\" {\n\t\tif currentMsgs, ok := req[\"messages\"].([]any); ok && len(currentMsgs) > 0 {\n\t\t\ts.timestampStore.Load(threadID)\n\n\t\t\t// Offset = cutoff - stubsCount: maps combined array index to raw position.\n\t\t\t// Raw position of message at combined index I = cutoff + (I - stubs).\n\t\t\t// msgN = rawPos + 1 = cutoff + (I - stubs) + 1 = (cutoff - stubs) + I + 1.\n\t\t\tmsgOffset := sawtoothCutoff - sawtoothStubs\n\n\t\t\ts.logger.Printf(\"[req %d %s] timestamps: rawTotal=%d, postSawtooth=%d, cutoff=%d, stubs=%d, offset=%d\", reqIdx, proj, rawMsgCount, len(currentMsgs), sawtoothCutoff, sawtoothStubs, msgOffset)\n\n\t\t\t// Pre-store assistant timestamp BEFORE inject loop so it's available immediately\n\t\t\ts.responseTsMu.RLock()\n\t\t\trespTime, hasRespTime := s.responseTimes[threadID]\n\t\t\ts.responseTsMu.RUnlock()\n\t\t\tif hasRespTime {\n\t\t\t\trespStr := shortWeekday(respTime.Weekday()) + \" \" + respTime.Format(\"2006-01-02 15:04:05\")\n\t\t\t\tfor j := len(currentMsgs) - 2; j >= 0; j-- {\n\t\t\t\t\tprevMsg, _ := currentMsgs[j].(map[string]any)\n\t\t\t\t\tif prevMsg == nil || prevMsg[\"role\"] != \"assistant\" {\n\t\t\t\t\t\tcontinue\n\t\t\t\t\t}\n\t\t\t\t\tif !msgHasTextBlock(prevMsg) {\n\t\t\t\t\t\ts.logger.Printf(\"[req %d %s] timestamps: skip assistant[%d] no text block\", reqIdx, proj, j)\n\t\t\t\t\t\tcontinue // skip tool-use-only assistant messages\n\t\t\t\t\t}\n\t\t\t\t\tprevMsgN := msgOffset + j + 1\n\t\t\t\t\tif _, exists := s.timestampStore.Get(threadID, prevMsgN); !exists {\n\t\t\t\t\t\ts.timestampStore.Store(threadID, prevMsgN, &TimestampMeta{Timestamp: respStr})\n\t\t\t\t\t\ts.logger.Printf(\"[req %d %s] timestamps: stored assistant msg:%d ts=%s\", reqIdx, proj, prevMsgN, respStr)\n\t\t\t\t\t} else {\n\t\t\t\t\t\ts.logger.Printf(\"[req %d %s] timestamps: assistant msg:%d already has ts\", reqIdx, proj, prevMsgN)\n\t\t\t\t\t}\n\t\t\t\t\tbreak\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\ts.logger.Printf(\"[req %d %s] timestamps: no responseTimes for thread\", reqIdx, proj)\n\t\t\t}\n\n\t\t\t// Inject [msg:N] (+ timestamp/delta if stored) on all PREVIOUS messages\n\t\t\tif n := InjectTimestamps(s.timestampStore, threadID, currentMsgs, len(currentMsgs)-1, msgOffset, sawtoothStubs); n > 0 {\n\t\t\t\tneedsReserialization = true\n\t\t\t\ts.logger.Printf(\"[req %d %s] timestamps: injected %d\", reqIdx, proj, n)\n\t\t\t}\n\n\t\t\t// Current (last) user message: full timestamp + delta + msg:N from raw count\n\t\t\tlastMsg, _ := currentMsgs[len(currentMsgs)-1].(map[string]any)\n\t\t\tif lastMsg != nil && lastMsg[\"role\"] == \"user\" && !msgHasMetaPrefix(lastMsg) {\n\t\t\t\tnow := time.Now()\n\t\t\t\tnowStr := shortWeekday(now.Weekday()) + \" \" + now.Format(\"2006-01-02 15:04:05\")\n\t\t\t\tentry := &TimestampMeta{Timestamp: nowStr}\n\t\t\t\tif hasRespTime {\n\t\t\t\t\tentry.Delta = formatDelta(now.Sub(respTime))\n\t\t\t\t}\n\t\t\t\ts.timestampStore.Store(threadID, rawMsgCount, entry)\n\t\t\t\tprependMeta(lastMsg, BuildMeta(rawMsgCount, entry))\n\t\t\t\tneedsReserialization = true\n\t\t\t}\n\n\t\t\ts.timestampStore.Persist(threadID)\n\t\t}\n\t}\n\n\t// Associative context injection (for ALL requests, not just stubbed ones)\n\t// Re-enabled with higher thresholds (55/75, max 1) after disabling Fresh Remember.\n\tassocContext := s.findAssociativeContextFor(userQuery, proj, threadID, messages)\n\tif assocContext != \"\" {\n\t\tcurrentMessages, _ := req[\"messages\"].([]any)\n\t\tif currentMessages == nil {\n\t\t\tcurrentMessages = messages\n\t\t}\n\t\treq[\"messages\"] = injectAssociativeContext(currentMessages, assocContext, s.cfg.SawtoothEnabled)\n\t\tneedsReserialization = true\n\t\ts.logger.Printf(\"%s[req %d %s tid=%s] associative context injected%s\", colorBlue, reqIdx, proj, threadID, colorReset)\n\t}\n\n\t// Doc-chunk context injection: separate search path for indexed docs (not skills)\n\tdocContext := s.findDocContextFor(userQuery, proj, messages)\n\tif docContext != \"\" {\n\t\tcurrentMessages, _ := req[\"messages\"].([]any)\n\t\tif currentMessages == nil {\n\t\t\tcurrentMessages = messages\n\t\t}\n\t\treq[\"messages\"] = injectAssociativeContext(currentMessages, docContext, s.cfg.SawtoothEnabled)\n\t\tneedsReserialization = true\n\t\ts.logger.Printf(\"%s[req %d %s tid=%s] doc context injected%s\", colorBlue, reqIdx, proj, threadID, colorReset)\n\t}\n\n\t// Fresh Remember Injection: DISABLED\n\t// Causes echo-loop: Claude saves via remember(), sees it again next turn as\n\t// [yesmem fresh memory], reacts to own output instead of user's request.\n\t// Recovery after /clear is handled by recovery.go (briefing), not here.\n\t// Subagents get their own briefing. No remaining use-case.\n\t// remItems := s.popRecentRemember()\n\tvar remItems []recentLearningItem\n\n\t// Rules re-injection: inject condensed CLAUDE.md rules every ~40k output tokens\n\tif rulesBlock := s.rulesInject(threadID, totalTokens, proj); rulesBlock != \"\" {\n\t\tcurrentMessages, _ := req[\"messages\"].([]any)\n\t\tif currentMessages == nil {\n\t\t\tcurrentMessages = messages\n\t\t}\n\t\treq[\"messages\"] = injectAssociativeContext(currentMessages, s.formatRulesReminder(rulesBlock, proj), s.cfg.SawtoothEnabled)\n\t\tneedsReserialization = true\n\t\ts.logger.Printf(\"%s[req %d %s tid=%s] rules reminder injected%s\", colorBlue, reqIdx, proj, threadID, colorReset)\n\t}\n\n\t// Plan auto-activation: nudge Claude to call set_plan() when reading a plan file\n\tif planFile := detectPlanFileRead(messages); planFile != \"\" {\n\t\tif activePlan, _ := s.getActivePlan(threadID); shouldNudgePlan(planFile, activePlan) {\n\t\t\tAppendSystemBlock(req, \"plan-nudge\",\n\t\t\t\tfmt.Sprintf(\"Du hast einen Implementierungsplan gelesen (%s). Aktiviere ihn jetzt via set_plan() damit Plan-Checkpoints funktionieren und dein Fortschritt getrackt wird.\", filepath.Base(planFile)))\n\t\t\tneedsReserialization = true\n\t\t\ts.logger.Printf(\"%s[req %d %s tid=%s] plan nudge injected for %s%s\", colorBlue, reqIdx, proj, threadID, filepath.Base(planFile), colorReset)\n\t\t}\n\t}\n\n\t// Plan checkpoint: inject plan reminder every ~20k tokens if active plan exists\n\ts.detectPlanToolCall(messages, threadID, totalTokens)\n\tif checkpoint := s.planCheckpointInject(threadID, totalTokens); checkpoint != \"\" {\n\t\tcurrentMessages, _ := req[\"messages\"].([]any)\n\t\tif currentMessages == nil {\n\t\t\tcurrentMessages = messages\n\t\t}\n\t\treq[\"messages\"] = injectAssociativeContext(currentMessages, checkpoint, s.cfg.SawtoothEnabled)\n\t\tneedsReserialization = true\n\t\ts.logger.Printf(\"%s[req %d %s tid=%s] plan checkpoint injected%s\", colorBlue, reqIdx, proj, threadID, colorReset)\n\t}\n\n\t// Docs hint injection: inject docs-available reminder every ~10k tokens (independent of plan)\n\tif docsHint := s.docsHintInject(threadID, totalTokens, proj); docsHint != \"\" {\n\t\tcurrentMessages, _ := req[\"messages\"].([]any)\n\t\tif currentMessages == nil {\n\t\t\tcurrentMessages = messages\n\t\t}\n\t\treq[\"messages\"] = injectAssociativeContext(currentMessages, docsHint, s.cfg.SawtoothEnabled)\n\t\tneedsReserialization = true\n\t\ts.logger.Printf(\"%s[req %d %s tid=%s] docs hint injected%s\", colorBlue, reqIdx, proj, threadID, colorReset)\n\t}\n\tif len(remItems) > 0 {\n\t\tvar lines []string\n\t\tfor _, item := range remItems {\n\t\t\tif item.ID > 0 {\n\t\t\t\tlines = append(lines, fmt.Sprintf(\"- [ID:%d] %s\", item.ID, truncateStr(item.Text, 200)))\n\t\t\t} else {\n\t\t\t\tlines = append(lines, \"- \"+truncateStr(item.Text, 200))\n\t\t\t}\n\t\t}\n\t\tremContext := fmt.Sprintf(\"[yesmem fresh memory]\\nGerade gelernt:\\n%s\\n%s\\n[/yesmem fresh memory]\", strings.Join(lines, \"\\n\"), InlineReflectionHint)\n\t\tcurrentMessages, _ := req[\"messages\"].([]any)\n\t\tif currentMessages == nil {\n\t\t\tcurrentMessages = messages\n\t\t}\n\t\treq[\"messages\"] = injectAssociativeContext(currentMessages, remContext, s.cfg.SawtoothEnabled)\n\t\tneedsReserialization = true\n\t\ts.logger.Printf(\"%s[req %d %s tid=%s] fresh remember injected (%d items)%s\", colorBlue, reqIdx, proj, threadID, len(remItems), colorReset)\n\t}\n\n\t// Skill eval injection: mandatory evaluation instruction for real user input only\n\tif threadID != \"\" && proj != \"\" && isUserInputTurn(messages) {\n\t\t// First: detect any skill activations from message history (updates tracker)\n\t\ts.syncSkillActivations(messages, proj, threadID)\n\n\t\t// Inject mandatory skill evaluation block into last user message\n\t\tskillEval := buildSkillEvalBlock()\n\t\tcurrentMessages, _ := req[\"messages\"].([]any)\n\t\tif currentMessages == nil {\n\t\t\tcurrentMessages = messages\n\t\t}\n\t\treq[\"messages\"] = injectAssociativeContext(currentMessages, skillEval, s.cfg.SawtoothEnabled)\n\t\tneedsReserialization = true\n\t\ts.logger.Printf(\"%s[req %d %s tid=%s] skill-eval injected%s\", colorBlue, reqIdx, proj, threadID, colorReset)\n\t}\n\n\t// === Collect injected learning IDs for next turn's inline reflection ===\n\tif threadID != \"\" {\n\t\tcurrentIDs := make(map[int64]string) // id → source\n\t\t// Source 1: Briefing learnings (cached, same every turn)\n\t\ts.briefingMu.RLock()\n\t\tbriefingSnapshot := s.briefingText\n\t\ts.briefingMu.RUnlock()\n\t\tif briefingSnapshot != \"\" {\n\t\t\tfor _, m := range idPattern.FindAllStringSubmatch(briefingSnapshot, -1) {\n\t\t\t\tif id, err := strconv.ParseInt(m[1], 10, 64); err == nil {\n\t\t\t\t\tcurrentIDs[id] = \"briefing\"\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\t// Source 2: Associative context (contextual, per-turn)\n\t\tif assocContext != \"\" {\n\t\t\tfor _, m := range idPattern.FindAllStringSubmatch(assocContext, -1) {\n\t\t\t\tif id, err := strconv.ParseInt(m[1], 10, 64); err == nil {\n\t\t\t\t\tcurrentIDs[id] = \"associative\" // overwrites briefing if both — associative is the stronger signal\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\t// Source 3: Fresh remember items (just learned)\n\t\tfor _, item := range remItems {\n\t\t\tif item.ID > 0 {\n\t\t\t\tcurrentIDs[item.ID] = \"fresh\"\n\t\t\t}\n\t\t}\n\t\ts.lastInjectedIDsMu.Lock()\n\t\tif len(currentIDs) > 0 {\n\t\t\t// Accumulate IDs across turns — don't overwrite.\n\t\t\t// Claude may reference an ID from turn 30 in turn 50.\n\t\t\texisting := s.lastInjectedIDs[threadID]\n\t\t\tif existing == nil {\n\t\t\t\texisting = make(map[int64]string)\n\t\t\t}\n\t\t\tfor id, source := range currentIDs {\n\t\t\t\texisting[id] = source\n\t\t\t}\n\t\t\ts.lastInjectedIDs[threadID] = existing\n\t\t}\n\t\ts.lastInjectedIDsMu.Unlock()\n\t}\n\n\t// Think reminder injection — only for real user turns, not tool continuations\n\tif threadID != \"\" {\n\t\tcurrentMessages, _ := req[\"messages\"].([]any)\n\t\tif currentMessages == nil {\n\t\t\tcurrentMessages = messages\n\t\t}\n\t\tif lastUserHasText(currentMessages) {\n\t\t\tthinkReminder := s.buildThinkReminder(threadID, threadID, false)\n\t\t\tif thinkReminder != \"\" {\n\t\t\t\treq[\"messages\"] = injectAssociativeContext(currentMessages, thinkReminder, s.cfg.SawtoothEnabled)\n\t\t\t\tneedsReserialization = true\n\t\t\t}\n\t\t}\n\n\t\t// Dialog injection: append at end of messages with pseudo-padding for alternation.\n\t\t// Always works regardless of tool_result chains or conversation state.\n\t\tdr := s.checkDialogMessages(threadID, proj)\n\t\tif dr.Extra != \"\" {\n\t\t\t// Prepend session ID for agent-to-agent communication\n\t\t\tdr.Extra = fmt.Sprintf(\"DEINE_SESSION_ID: %s\\n\", threadID) + dr.Extra\n\t\t\tinjected := false\n\t\t\tif msgs, ok := req[\"messages\"].([]any); ok && len(msgs) > 0 {\n\t\t\t\t// Pad with empty assistant if last message is user (maintains alternation)\n\t\t\t\tlastMsg, _ := msgs[len(msgs)-1].(map[string]any)\n\t\t\t\tif lastMsg != nil && lastMsg[\"role\"] == \"user\" {\n\t\t\t\t\tmsgs = append(msgs, map[string]any{\n\t\t\t\t\t\t\"role\": \"assistant\",\n\t\t\t\t\t\t\"content\": \"​\", // zero-width space — invisible padding\n\t\t\t\t\t})\n\t\t\t\t}\n\t\t\t\t// Append dialog as new user message (becomes the last message Claude sees)\n\t\t\t\tmsgs = append(msgs, map[string]any{\n\t\t\t\t\t\"role\": \"user\",\n\t\t\t\t\t\"content\": \"\\n\" + dr.Extra + \"\\n\",\n\t\t\t\t})\n\t\t\t\treq[\"messages\"] = msgs\n\t\t\t\tneedsReserialization = true\n\t\t\t\tinjected = true\n\t\t\t}\n\t\t\t// Mark as read after 3 injection turns — long enough for Claude to process,\n\t\t\t// resilient against fast ping-pong sessions (time-based was broken for <30s turns).\n\t\t\tif injected && dr.HasUnread && dr.SessionID != \"\" {\n\t\t\t\tif s.shouldMarkChannelRead(dr) {\n\t\t\t\t\ts.markDialogRead(dr)\n\t\t\t\t}\n\t\t\t}\n\t\t\ts.logger.Printf(\"[dialog] injection injected=%v extra_len=%d\", injected, len(dr.Extra))\n\t\t}\n\t}\n\n\t// Signal tools are NOT injected into the main request.\n\t// They are collected via a separate async reflection call after the response.\n\t// See fireReflectionCall() in reflection.go.\n\t// NEVER put _signal_* tools back here — the model ignores them among 44+ real tools.\n\n\t// Legacy assistant annotation: timestamp on last assistant message after cache breakpoint.\n\t// User timestamps are now handled by TimestampStore (injected earlier in the pipeline).\n\tif threadID != \"\" {\n\t\tif msgs, ok := req[\"messages\"].([]any); ok && len(msgs) > 0 {\n\t\t\t// Find last cache_control breakpoint in messages\n\t\t\tcacheIdx := -1\n\t\t\tfor i, m := range msgs {\n\t\t\t\tmsg, _ := m.(map[string]any)\n\t\t\t\tif msg == nil {\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\t\t\t\tif _, has := msg[\"cache_control\"]; has {\n\t\t\t\t\tcacheIdx = i\n\t\t\t\t}\n\t\t\t\tif content, ok := msg[\"content\"].([]any); ok {\n\t\t\t\t\tfor _, block := range content {\n\t\t\t\t\t\tif b, ok := block.(map[string]any); ok {\n\t\t\t\t\t\t\tif _, has := b[\"cache_control\"]; has {\n\t\t\t\t\t\t\t\tcacheIdx = i\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t\tstartIdx := cacheIdx + 1\n\t\t\tif startIdx < 0 {\n\t\t\t\tstartIdx = 0\n\t\t\t}\n\t\t\ts.responseTsMu.RLock()\n\t\t\trespTime, hasRespTime := s.responseTimes[threadID]\n\t\t\ts.responseTsMu.RUnlock()\n\t\t\tif hasRespTime {\n\t\t\t\tfor i := len(msgs) - 1; i >= startIdx; i-- {\n\t\t\t\t\tmsg, _ := msgs[i].(map[string]any)\n\t\t\t\t\tif msg != nil && msg[\"role\"] == \"assistant\" {\n\t\t\t\t\t\tmeta := fmt.Sprintf(\"[%s] [msg:%d]\", respTime.Format(\"2006-01-02 15:04:05\"), i)\n\t\t\t\t\t\tprependMeta(msg, meta)\n\t\t\t\t\t\tneedsReserialization = true\n\t\t\t\t\t\tbreak\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\t// Self-Priming: DISABLED (experimental, see signal_handlers.go comment block).\n\t// The reflection-based selfprime (Haiku) was too inaccurate to be useful —\n\t// the main model adapts to user context organically from message history.\n\t// Kept for reference; re-enable by uncommenting below + setting ShouldActivate=true.\n\t//\n\t// s.selfPrimeMu.RLock()\n\t// anchor := s.selfPrimes[threadID]\n\t// s.selfPrimeMu.RUnlock()\n\t// if anchor != \"\" {\n\t// \tif msgs, ok := req[\"messages\"].([]any); ok && len(msgs) > 0 {\n\t// \t\tlastMsg, _ := msgs[len(msgs)-1].(map[string]any)\n\t// \t\tif lastMsg != nil && lastMsg[\"role\"] == \"user\" {\n\t// \t\t\tprimeBlock := map[string]any{\n\t// \t\t\t\t\"type\": \"text\",\n\t// \t\t\t\t\"text\": \"[self-prime]\\n[Self-Prime von deiner letzten Antwort]: \" + anchor,\n\t// \t\t\t}\n\t// \t\t\tif content, ok := lastMsg[\"content\"].([]any); ok {\n\t// \t\t\t\tlastMsg[\"content\"] = append(content, primeBlock)\n\t// \t\t\t} else if text, ok := lastMsg[\"content\"].(string); ok {\n\t// \t\t\t\tlastMsg[\"content\"] = []any{\n\t// \t\t\t\t\tmap[string]any{\"type\": \"text\", \"text\": text},\n\t// \t\t\t\t\tprimeBlock,\n\t// \t\t\t\t}\n\t// \t\t\t}\n\t// \t\t\tneedsReserialization = true\n\t// \t\t}\n\t// \t}\n\t// }\n\n\t// Prompt Caching: inject cache_control breakpoints on system + tools\n\t// Sawtooth: disabled (all 4 slots are used by CC system (2) + briefing (1) + CC messages (1))\n\t// Legacy: count existing breakpoints and fill up remaining slots.\n\tif !s.cfg.SawtoothEnabled && s.cacheGate.ShouldCache() {\n\t\tif n := InjectCacheBreakpoints(req, s.logger); n > 0 {\n\t\t\tneedsReserialization = true\n\t\t\ts.logger.Printf(\"[req %d %s] prompt cache: %d breakpoints injected\", reqIdx, proj, n)\n\t\t} else {\n\t\t\ts.logger.Printf(\"[req %d %s] prompt cache: no budget (4/4 already set)\", reqIdx, proj)\n\t\t}\n\t}\n\n\t// Final: upgrade cache TTL + enforce breakpoint limit (must run AFTER all injections + InjectCacheBreakpoints)\n\t// Shift the last messages-breakpoint from unstable user message to stable assistant message.\n\t// Text user messages change between turns (timestamps, reminders, structure); assistant messages don't.\n\t// Tool-result messages are excluded — they are stable cache anchors.\n\tif ShiftMessageBreakpoint(req) {\n\t\tneedsReserialization = true\n\t}\n\t// TTL upgrade only for API key auth — subscription (OAuth) users should not get extended TTL.\n\tif s.cfg.CacheTTL != \"\" && s.cfg.CacheTTL != \"ephemeral\" && IsAPIKeyAuth(r.Header) {\n\t\tif n := UpgradeCacheTTL(req, s.cfg.CacheTTL); n > 0 {\n\t\t\tneedsReserialization = true\n\t\t\ts.logger.Printf(\"[req %d %s] cache TTL upgraded: %d blocks → %s\", reqIdx, proj, n, s.cfg.CacheTTL)\n\t\t}\n\t}\n\tif n := EnforceCacheBreakpointLimit(req, maxCacheBreakpoints); n > 0 {\n\t\tneedsReserialization = true\n\t\ts.logger.Printf(\"[req %d %s] prompt cache: trimmed %d surplus breakpoints\", reqIdx, proj, n)\n\t}\n\n\t// Pre-flight: validate tool_use/tool_result pairing before forwarding.\n\t// Repairs orphans created by Stubify/Collapse/Injection pipeline.\n\tif msgs, ok := req[\"messages\"].([]any); ok {\n\t\trepaired, orphans := validateToolPairs(msgs, s.logger)\n\t\tif orphans > 0 {\n\t\t\treq[\"messages\"] = repaired\n\t\t\ts.logger.Printf(\"%s[req %d %s tid=%s] validate: repaired %d orphan tool_result(s)%s\", colorOrange, reqIdx, proj, threadID, orphans, colorReset)\n\t\t\tneedsReserialization = true\n\t\t}\n\t}\n\n\t// Early-out: skip re-serialization when nothing changed\n\tif !needsReserialization {\n\t\ts.logger.Printf(\"%s[req %d %s tid=%s] %d messages, ~%dk tokens (no stubbing, threshold=%dk)%s\", colorDim, reqIdx, proj, threadID, len(messages), totalTokens/1000, s.effectiveTokenThreshold(model)/1000, colorReset)\n\t\ts.forwardWithAnnotation(w, r, body, reqIdx, toolUseIDs, proj, threadID, msgCount, totalTokens)\n\t\treturn\n\t}\n\n\t// Re-serialize\n\tbody, err = json.Marshal(req)\n\tif err != nil {\n\t\ts.logger.Printf(\"%s[req %d %s tid=%s] re-marshal error: %v%s\", colorRed, reqIdx, proj, threadID, err, colorReset)\n\t\thttp.Error(w, \"internal proxy error\", http.StatusInternalServerError)\n\t\treturn\n\t}\n\n\t// Debug: dump final request body to file (enable with YESMEM_PROXY_DEBUG=1)\n\tif needsReserialization && os.Getenv(\"YESMEM_PROXY_DEBUG\") == \"1\" {\n\t\tdumpDir := filepath.Join(s.cfg.DataDir, \"debug\")\n\t\tos.MkdirAll(dumpDir, 0755)\n\t\tts := time.Now().Format(\"20060102-150405\")\n\t\tdumpPath := fmt.Sprintf(\"%s/req_%s_%d.json\", dumpDir, ts, reqIdx)\n\t\tos.WriteFile(dumpPath, body, 0644)\n\t\ts.logger.Printf(\"[req %d] dumped %dk to %s\", reqIdx, len(body)/1024, dumpPath)\n\t}\n\n\t// Task #7: Mark this request fingerprint for retry detection\n\tif !isRetryReq {\n\t\ts.markRequest(fp)\n\t}\n\n\t// Forward to Anthropic with SSE annotation extraction\n\t// Use original msgCount (pre-injection) so actual-based estimation stays consistent across turns\n\t// Use actual-based totalTokens as estimate when available (more accurate than full recount)\n\tfinalEstimate := totalTokens\n\tif s.sawtoothTrigger == nil || s.sawtoothTrigger.GetLastTokens(threadID) == 0 {\n\t\t// First request: no actual yet, do a full recount of post-injection messages\n\t\tfinalMsgs, _ := req[\"messages\"].([]any)\n\t\tfinalEstimate = s.measureOverhead(req) + s.countMessageTokens(finalMsgs)\n\t}\n\ts.logger.Printf(\"[req %d %s tid=%s] FINAL: ~%dk estimated (after all injections)\", reqIdx, proj, threadID, finalEstimate/1000)\n\tif raw := s.getRawEstimate(threadID); raw > 0 {\n\t\ts.cacheStatusWriter.UpdateRawForThread(threadID, raw)\n\t}\n\ts.forwardWithAnnotation(w, r, body, reqIdx, toolUseIDs, proj, threadID, msgCount, finalEstimate)\n}\n\n// bytesPerToken is the approximation factor for token estimation.\n// Anthropic recommends ~3.5 chars/token; we use 3.6 for slight overestimation\n// (triggers stubbing a bit earlier = safer for context budget).\nconst bytesPerToken = 3.6\n\n// TokenEstimateFunc is the central token estimation function.\n// All stubbing/budget decisions use this. Receives extracted text content\n// (not JSON wire format) so the tokenizer counts actual content tokens.\ntype TokenEstimateFunc func(text string) int\n"}, "f1f4f81ed75d61a9b1eccba50aec9beef6731a62": {"t": 1785759369.2716227, "n": 53484, "h": "cbe379ae659e1131", "c": "package proxy\n\nimport (\n\t\"context\"\n\t\"crypto/tls\"\n\t\"encoding/json\"\n\t\"fmt\"\n\t\"io\"\n\t\"log\"\n\t\"net\"\n\t\"net/http\"\n\t\"os\"\n\t\"os/signal\"\n\t\"path/filepath\"\n\t\"strconv\"\n\t\"strings\"\n\t\"sync\"\n\t\"sync/atomic\"\n\t\"syscall\"\n\t\"time\"\n\n\ttokenizer \"github.com/qhenkart/anthropic-tokenizer-go\"\n)\n\n// Config holds proxy configuration.\ntype Config struct {\n\tListenAddr string // e.g. \":9099\"\n\tTargetURL string // e.g. \"https://api.anthropic.com\"\n\tTokenThreshold int // trigger stubbing above this estimated token count\n\tTokenMinimumThreshold int // stub down to this floor\n\tTokenThresholds map[string]int // model-specific thresholds: {\"opus\": 180000, \"haiku\": 130000}\n\tKeepRecent int // number of messages to always keep unmodified\n\tDataDir string // yesmem data directory for DB access\n\tOpenAITargetURL string // upstream for OpenAI-format clients; if empty, uses TargetURL\n\n\t// Signal reflection\n\tSignalsEnabled bool // enable async signal reflection calls\n\tSignalsMode string // \"reflection\" = separate API call\n\tSignalsEveryNTurns int // every N end_turn responses\n\tSignalsModel string // full model ID, e.g. \"claude-sonnet-4-6\"\n\tAPIKey string // needed for reflection API calls\n\n\t// Sawtooth cache optimization\n\tSawtoothEnabled bool // use sawtooth instead of progressive decay (default: true)\n\tCacheTTL string // \"ephemeral\" (5m) or \"1h\" (extended)\n\n\t// Usage deflation: scale down input_tokens reported to CC to suppress \"Context low\" warning.\n\t// 0 = disabled, 0.7 = report 70% of actual tokens. Real values kept for internal tracking.\n\tUsageDeflationFactor float64\n\n\t// System prompt rewriting\n\tPromptUngate bool // strip \"may or may not be relevant\" disclaimer from CLAUDE.md injection\n\tPromptRewrite bool // strip \"Output efficiency\" + \"short and concise\", inject Ant-quality directives\n\tPromptEnhance bool // CLAUDE.md authority reinforcement, comment discipline, persona-based tone\n\tEffortFloor string // minimum effort level: \"low\", \"medium\", \"high\", \"max\" (empty = off)\n\n\t// Cache keepalive\n\tCacheKeepaliveEnabled bool\n\tCacheKeepaliveMode string // \"auto\", \"5m\", \"1h\"\n\tCacheKeepalivePings5m int\n\tCacheKeepalivePings1h int\n\n\t// Forked agents\n\tForkedAgentsEnabled bool\n\tForkedAgentsModel string // full model ID\n\tForkedAgentsTokenGrowthTrigger int\n\tForkedAgentsMaxFailures int\n\tForkedAgentsMaxForksPerSession int\n\tForkedAgentsDebug bool\n\n\t// Cache state management\n\tResetCache bool // clear persisted frozen stubs and decay state on startup\n}\n\nconst maxAnnotations = 5000 // evict oldest when exceeded\n\n// ANSI color codes for log output\nconst (\n\tcolorReset = \"\\033[0m\"\n\tcolorDim = \"\\033[2m\" // gray — passthrough / no stubbing\n\tcolorGreen = \"\\033[32m\" // stubbed / compressed\n\tcolorLightGreen = \"\\033[92m\" // FINAL without compression\n\tcolorBlue = \"\\033[34m\" // injection\n\tcolorOrange = \"\\033[33m\" // skip / retry\n\tcolorRed = \"\\033[31m\" // errors\n)\n\n// Server is the infinite-thread proxy.\ntype Server struct {\n\tcfg Config\n\thttpClient *http.Client\n\tlogger *log.Logger\n\trequestIdx atomic.Int64\n\n\t// Post-hoc annotations: tool_use_id → first ~120 chars of Claude's response\n\tmu sync.RWMutex\n\tannotations map[string]string\n\n\t// Progressive decay tracker\n\tdecay *DecayTracker\n\n\t// Living narrative block\n\tnarrative *Narrative\n\n\t// Pivot moments cache (from daemon)\n\tpivotMu sync.RWMutex\n\tpivotTexts []string\n\tpivotCached time.Time\n\n\t// Aggregate stats (Task #2)\n\tstats *ProxyStats\n\n\t// Local tokenizer for accurate token counting\n\ttokenizer *tokenizer.Tokenizer\n\n\t// Cached overhead tokens (system + tools), set on first request per thread\n\toverheadMu sync.RWMutex\n\toverheadTokens int // unused, kept for interface compat\n\n\t// Hysteresis state for stubbing (Task #6)\n\tstubActive atomic.Bool\n\n\t// Daemon connection state — first successful connect flips to true\n\tdaemonReady atomic.Bool\n\n\t// Retry detection (Task #7)\n\tretryMu sync.RWMutex\n\tlastFingerprint string\n\n\t// Session start time for temporal annotation in archive blocks\n\tsessionStartMu sync.RWMutex\n\tsessionStartTime time.Time\n\n\t// Runtime config override (set via MCP set_config tool)\n\tconfigOverrideMu sync.RWMutex\n\ttokenThresholdOverrides map[string]int // model-key → threshold, \"\" = global fallback\n\n\t// Briefing cache — loaded once per thread, injected as user/assistant message pair\n\tbriefingMu sync.RWMutex\n\tbriefingText string\n\n\t// Cognitive signal bus — routes _signal_* tool calls to handlers\n\tsignalBus *SignalBus\n\n\t// Inline reflection: track which learning IDs were injected per thread\n\tlastInjectedIDsMu sync.Mutex\n\tlastInjectedIDs map[string]map[int64]string // threadID → id → source (\"briefing\"|\"associative\"|\"fresh\")\n\tsessionInjectCounts map[string]map[int64]int // threadID → learningID → injection count this session\n\tlastTurnInjected map[string]map[int64]bool // threadID → learningIDs injected in previous turn\n\n\t// Self-priming anchors: threadID → last self-prime text\n\tselfPrimeMu sync.RWMutex\n\tselfPrimes map[string]string\n\n\t// Timestamp tracking: threadID → time of last response completion\n\tresponseTsMu sync.RWMutex\n\tresponseTimes map[string]time.Time\n\n\t// Think reminder: per-thread request counter (replaces hook-think file-based counter)\n\tthinkMu sync.Mutex\n\tthinkCounters map[string]int // threadID → request count\n\n\tchannelMu sync.Mutex\n\tchannelInjectCount map[string]int // sessionID → injection turn count\n\n\t// Prompt cache gating — enables cache_control breakpoints when requests are frequent\n\tcacheGate *CacheGate\n\n\t// Sawtooth cache optimization\n\tfrozenStubs *FrozenStubs\n\tsawtoothTrigger *SawtoothTrigger\n\tcacheStatusWriter *CacheStatusWriter\n\tcacheKeepalive *CacheKeepalive\n\tcacheTTLDetector *CacheTTLDetector\n\n\t// Cumulative token savings from collapsing (per threadID)\n\trawSavingsMu sync.Mutex\n\trawSavings map[string]int\n\n\t// Skill hint tracking: per-thread active skills\n\tskillTracker *skillHintTracker\n\n\t// Forked agent state: per-thread token growth + failure tracking\n\tforkState *ForkState\n\tforkConfigs []ForkConfig\n\n\t// Rules re-injection: condensed CLAUDE.md rules injected every ~40k tokens\n\trulesMu sync.RWMutex\n\trulesBlock string // cached condensed rules (fetched once from daemon)\n\trulesTokenCount map[string]int // threadID → tokens since last rules injection\n\trulesCollapseInjected map[string]bool // threadID → true if collapse already injected rules (reset by normal inject)\n\tmsgCounters *msgCounters // global per-thread msg counter, persists across collapses\n\n\t// Loop detection: per-thread warning state\n\tloopMu sync.Mutex\n\tloopStates map[string]*LoopState // threadID → state\n}\n\n// Run starts the proxy server and blocks until interrupted.\nfunc Run(cfg Config) error {\n\ts := &Server{\n\t\tcfg: cfg,\n\t\thttpClient: &http.Client{\n\t\t\tTimeout: 10 * time.Minute,\n\t\t\tTransport: &http.Transport{\n\t\t\t\tTLSClientConfig: &tls.Config{},\n\t\t\t\tMaxIdleConns: 10,\n\t\t\t\tIdleConnTimeout: 90 * time.Second,\n\t\t\t\tDisableCompression: true,\n\t\t\t\tResponseHeaderTimeout: 60 * time.Second,\n\t\t\t},\n\t\t},\n\t\tlogger: createLogger(cfg.DataDir),\n\t\tannotations: make(map[string]string),\n\t\tdecay: NewDecayTracker(),\n\t\tnarrative: NewNarrative(),\n\t\tstats: &ProxyStats{startTime: time.Now()},\n\t\tselfPrimes: make(map[string]string),\n\t\tlastInjectedIDs: make(map[string]map[int64]string),\n\t\tsessionInjectCounts: make(map[string]map[int64]int),\n\t\tlastTurnInjected: make(map[string]map[int64]bool),\n\t\tresponseTimes: make(map[string]time.Time),\n\t\tthinkCounters: make(map[string]int),\n\t\tchannelInjectCount: make(map[string]int),\n\t\tcacheGate: NewCacheGate(cacheGapForTTL(cfg.CacheTTL)),\n\t\tfrozenStubs: NewFrozenStubsWithTTL(sawtoothTTLForCacheTTL(cfg.CacheTTL)),\n\t\tsawtoothTrigger: NewSawtoothTrigger(cacheGapForTTL(cfg.CacheTTL), cfg.TokenThreshold, cfg.TokenMinimumThreshold),\n\t\tcacheStatusWriter: NewCacheStatusWriter(cfg.DataDir, cfg.CacheTTL, cfg.TokenThreshold, cfg.TokenMinimumThreshold),\n\t\tcacheTTLDetector: NewCacheTTLDetectorWithPersist(cfg.DataDir),\n\t\tskillTracker: newSkillHintTracker(),\n\t\trulesTokenCount: make(map[string]int),\n\t\trulesCollapseInjected: make(map[string]bool),\n\t\tmsgCounters: newMsgCounters(),\n\t\tloopStates: make(map[string]*LoopState),\n\t\tforkState: NewForkState(cfg.ForkedAgentsTokenGrowthTrigger, cfg.ForkedAgentsMaxFailures, cfg.ForkedAgentsMaxForksPerSession),\n\t\tforkConfigs: []ForkConfig{},\n\t}\n\n\t// Log persisted detection state\n\tif sup := s.cacheTTLDetector.Is1hSupported(); sup != nil {\n\t\ts.logger.Printf(\"[proxy] loaded persisted TTL detection: 1h_supported=%v\", *sup)\n\t\tif *sup && s.frozenStubs != nil {\n\t\t\ts.frozenStubs.UpdateTTL(sawtoothTTLForCacheTTL(\"1h\"))\n\t\t}\n\t}\n\n\tif cfg.CacheKeepaliveEnabled {\n\t\ts.cacheKeepalive = NewCacheKeepalive(CacheKeepaliveConfig{\n\t\t\tTarget: cfg.TargetURL,\n\t\t\tMode: cfg.CacheKeepaliveMode,\n\t\t\tPings5m: cfg.CacheKeepalivePings5m,\n\t\t\tPings1h: cfg.CacheKeepalivePings1h,\n\t\t\tDetector: s.cacheTTLDetector,\n\t\t\tLogger: s.logger,\n\t\t\tOnPing: func(threadID string, cacheRead, cacheWrite int) {\n\t\t\t\ts.cacheTTLDetector.RecordResponse(cacheRead, cacheWrite, 0)\n\t\t\t\tif s.frozenStubs != nil {\n\t\t\t\t\ts.frozenStubs.Touch(threadID)\n\t\t\t\t}\n\t\t\t\tif s.sawtoothTrigger != nil {\n\t\t\t\t\ts.sawtoothTrigger.TouchRequestTime(threadID)\n\t\t\t\t}\n\t\t\t},\n\t\t})\n\t}\n\n\t// Wire detector + keepalive to status writer for terminal display\n\ts.cacheStatusWriter.SetDetector(s.cacheTTLDetector)\n\tif s.cacheKeepalive != nil {\n\t\ts.cacheStatusWriter.SetKeepalive(s.cacheKeepalive)\n\t}\n\n\t// Register forked agent configs\n\tif cfg.ForkedAgentsEnabled {\n\t\ts.forkConfigs = append(s.forkConfigs, NewExtractAndEvaluateConfig(cfg.ForkedAgentsModel))\n\t\tmodelDesc := cfg.ForkedAgentsModel\n\t\tif modelDesc == \"\" {\n\t\t\tmodelDesc = \"(same as main thread)\"\n\t\t}\n\t\ts.logger.Printf(\"[proxy] forked agents enabled: %d configs, model=%s, trigger=%dk tokens\",\n\t\t\tlen(s.forkConfigs), modelDesc, cfg.ForkedAgentsTokenGrowthTrigger/1000)\n\t}\n\n\t// Wire sawtooth token persistence via daemon RPC\n\ts.sawtoothTrigger.SetPersistFunc(func(key, value string) {\n\t\tif _, err := s.queryDaemon(\"set_proxy_state\", map[string]any{\"key\": key, \"value\": value}); err != nil {\n\t\t\ts.logger.Printf(\"[sawtooth] persist failed for %s: %v\", key, err)\n\t\t}\n\t})\n\ts.sawtoothTrigger.SetLoadFunc(func(key string) (string, bool) {\n\t\tresult, err := s.queryDaemon(\"get_proxy_state\", map[string]any{\"key\": key})\n\t\tif err != nil || result == nil {\n\t\t\treturn \"\", false\n\t\t}\n\t\tvar resp struct {\n\t\t\tValue string `json:\"value\"`\n\t\t}\n\t\tif json.Unmarshal(result, &resp) != nil || resp.Value == \"\" {\n\t\t\treturn \"\", false\n\t\t}\n\t\treturn resp.Value, true\n\t})\n\n\t// Wire frozen stubs persistence via same daemon RPC\n\ts.frozenStubs.SetPersistFunc(func(key, value string) {\n\t\tif _, err := s.queryDaemon(\"set_proxy_state\", map[string]any{\"key\": key, \"value\": value}); err != nil {\n\t\t\ts.logger.Printf(\"[frozen] persist failed for %s: %v\", key, err)\n\t\t}\n\t})\n\ts.frozenStubs.SetLoadFunc(func(key string) (string, bool) {\n\t\tresult, err := s.queryDaemon(\"get_proxy_state\", map[string]any{\"key\": key})\n\t\tif err != nil || result == nil {\n\t\t\treturn \"\", false\n\t\t}\n\t\tvar resp struct {\n\t\t\tValue string `json:\"value\"`\n\t\t}\n\t\tif json.Unmarshal(result, &resp) != nil || resp.Value == \"\" {\n\t\t\treturn \"\", false\n\t\t}\n\t\treturn resp.Value, true\n\t})\n\n\t// Wire decay tracker persistence via same daemon RPC\n\ts.decay.SetPersistFunc(func(key, value string) {\n\t\tif _, err := s.queryDaemon(\"set_proxy_state\", map[string]any{\"key\": key, \"value\": value}); err != nil {\n\t\t\ts.logger.Printf(\"[decay] persist failed for %s: %v\", key, err)\n\t\t}\n\t})\n\ts.decay.SetLoadFunc(func(key string) (string, bool) {\n\t\tresult, err := s.queryDaemon(\"get_proxy_state\", map[string]any{\"key\": key})\n\t\tif err != nil || result == nil {\n\t\t\treturn \"\", false\n\t\t}\n\t\tvar resp struct {\n\t\t\tValue string `json:\"value\"`\n\t\t}\n\t\tif json.Unmarshal(result, &resp) != nil || resp.Value == \"\" {\n\t\t\treturn \"\", false\n\t\t}\n\t\treturn resp.Value, true\n\t})\n\n\t// Clear persisted cache state if --reset-cache was passed\n\tif cfg.ResetCache {\n\t\tfor _, prefix := range []string{\"frozen:\", \"decay:\"} {\n\t\t\tresult, err := s.queryDaemon(\"delete_proxy_state_prefix\", map[string]any{\"prefix\": prefix})\n\t\t\tif err != nil {\n\t\t\t\ts.logger.Printf(\"[reset-cache] failed to delete %s*: %v\", prefix, err)\n\t\t\t} else {\n\t\t\t\tvar resp struct{ Deleted int }\n\t\t\t\tjson.Unmarshal(result, &resp)\n\t\t\t\ts.logger.Printf(\"[reset-cache] deleted %d %s* entries\", resp.Deleted, prefix)\n\t\t\t}\n\t\t}\n\t}\n\n\t// Init signal bus with daemon RPC as callback\n\ts.signalBus = NewSignalBus(s.logger)\n\tregisterSignalHandlers(s.signalBus, s.logger, s.queryDaemon, func(threadID, anchor string) {\n\t\ts.selfPrimeMu.Lock()\n\t\ts.selfPrimes[threadID] = anchor\n\t\ts.selfPrimeMu.Unlock()\n\t})\n\n\t// Init local tokenizer (BPE-based, ~60ms)\n\ttok, err := tokenizer.New()\n\tif err != nil {\n\t\ts.logger.Printf(\"%s[proxy] tokenizer init failed, falling back to heuristic: %v%s\", colorOrange, err, colorReset)\n\t} else {\n\t\ts.tokenizer = tok\n\t\ts.logger.Printf(\"[proxy] tokenizer initialized\")\n\t}\n\n\tmux := http.NewServeMux()\n\tmux.HandleFunc(\"/health\", s.handleHealth)\n\tmux.HandleFunc(\"/\", s.handleRequest)\n\n\tsrv := &http.Server{\n\t\tAddr: sanitizeListenAddr(cfg.ListenAddr),\n\t\tHandler: mux,\n\t}\n\n\tdone := make(chan os.Signal, 1)\n\tsignal.Notify(done, syscall.SIGINT, syscall.SIGTERM)\n\n\tgo func() {\n\t\t<-done\n\t\ts.logger.Println(\"shutting down (draining in-flight requests)...\")\n\t\tif s.cacheKeepalive != nil {\n\t\t\ts.cacheKeepalive.Stop()\n\t\t}\n\t\tctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)\n\t\tdefer cancel()\n\t\tif err := srv.Shutdown(ctx); err != nil {\n\t\t\ts.logger.Printf(\"graceful shutdown failed: %v, forcing close\", err)\n\t\t\tsrv.Close()\n\t\t}\n\t}()\n\n\ts.logger.Printf(\"listening on %s → %s (threshold=%d, keepRecent=%d, model_thresholds=%v)\",\n\t\tsanitizeListenAddr(cfg.ListenAddr), cfg.TargetURL, cfg.TokenThreshold, cfg.KeepRecent, cfg.TokenThresholds)\n\n\tif err := srv.ListenAndServe(); err != http.ErrServerClosed {\n\t\treturn fmt.Errorf(\"listen: %w\", err)\n\t}\n\treturn nil\n}\n\n// effectiveTokenThreshold returns the runtime token threshold for a given model.\n// Priority: MCP override (model-specific) > MCP override (global) > config model-specific > config global.\nfunc (s *Server) effectiveTokenThreshold(model string) int {\n\ts.configOverrideMu.RLock()\n\toverrides := s.tokenThresholdOverrides\n\ts.configOverrideMu.RUnlock()\n\tif len(overrides) > 0 && model != \"\" {\n\t\tlower := strings.ToLower(model)\n\t\tfor key, threshold := range overrides {\n\t\t\tif key != \"\" && strings.Contains(lower, strings.ToLower(key)) && threshold > 0 {\n\t\t\t\treturn threshold\n\t\t\t}\n\t\t}\n\t}\n\t// Global override fallback (key \"\")\n\tif len(overrides) > 0 {\n\t\tif globalOverride, ok := overrides[\"\"]; ok && globalOverride > 0 {\n\t\t\treturn globalOverride\n\t\t}\n\t}\n\tif model != \"\" && len(s.cfg.TokenThresholds) > 0 {\n\t\tlower := strings.ToLower(model)\n\t\tfor key, threshold := range s.cfg.TokenThresholds {\n\t\t\tif strings.Contains(lower, strings.ToLower(key)) {\n\t\t\t\treturn threshold\n\t\t\t}\n\t\t}\n\t}\n\treturn s.cfg.TokenThreshold\n}\n\n// SetTokenThresholdOverride sets a runtime override for the token threshold per model key.\n// Pass threshold=0 to reset the override for that key. Key \"\" is the global fallback.\nfunc (s *Server) SetTokenThresholdOverride(modelKey string, threshold int) {\n\ts.configOverrideMu.Lock()\n\tif s.tokenThresholdOverrides == nil {\n\t\ts.tokenThresholdOverrides = make(map[string]int)\n\t}\n\tif threshold <= 0 {\n\t\tdelete(s.tokenThresholdOverrides, modelKey)\n\t} else {\n\t\ts.tokenThresholdOverrides[modelKey] = threshold\n\t}\n\ts.configOverrideMu.Unlock()\n}\n\n// refreshConfigOverrides loads runtime config overrides from daemon proxy_state.\n// Checks session-specific override first (by threadID), then falls back to global.\nfunc (s *Server) refreshConfigOverrides(threadID string) {\n\t// Try session-specific first\n\tif threadID != \"\" {\n\t\tresult, err := s.queryDaemon(\"get_proxy_state\", map[string]any{\"key\": \"config_override:token_threshold:\" + threadID})\n\t\tif err == nil && result != nil {\n\t\t\tvar resp struct {\n\t\t\t\tValue string `json:\"value\"`\n\t\t\t}\n\t\t\tif json.Unmarshal(result, &resp) == nil && resp.Value != \"\" {\n\t\t\t\tif v, err := strconv.Atoi(resp.Value); err == nil && v > 0 {\n\t\t\t\t\ts.SetTokenThresholdOverride(\"\", v)\n\t\t\t\t\treturn\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\t// Fall back to global\n\tresult, err := s.queryDaemon(\"get_proxy_state\", map[string]any{\"key\": \"config_override:token_threshold\"})\n\tif err != nil || result == nil {\n\t\treturn\n\t}\n\tvar resp struct {\n\t\tValue string `json:\"value\"`\n\t}\n\tif json.Unmarshal(result, &resp) == nil && resp.Value != \"\" {\n\t\tif v, err := strconv.Atoi(resp.Value); err == nil && v > 0 {\n\t\t\ts.SetTokenThresholdOverride(\"\", v)\n\t\t}\n\t}\n}\n\n// queryDaemon opens a fresh unix socket connection per call (thread-safe, no import cycle).\n// ensureFrozenPersisted checks if frozen stubs for a thread are in the DB.\n// If missing (e.g. daemon was unreachable after deploy), re-persists them.\n// Runs asynchronously to avoid blocking the request path.\nfunc (s *Server) ensureFrozenPersisted(threadID string) {\n\tgo func() {\n\t\tkey := \"frozen:\" + threadID\n\t\tresult, err := s.queryDaemon(\"get_proxy_state\", map[string]any{\"key\": key})\n\t\tif err != nil {\n\t\t\treturn // daemon still unreachable, will retry next request\n\t\t}\n\t\tvar resp struct{ Value string }\n\t\tif json.Unmarshal(result, &resp) == nil && resp.Value != \"\" {\n\t\t\treturn // already persisted\n\t\t}\n\t\t// Not in DB — trigger re-persist from in-memory state\n\t\ts.frozenStubs.Persist(threadID)\n\t\ts.decay.Persist(threadID)\n\t\ts.logger.Printf(\"[frozen] re-persisted stubs for %s (was missing from DB)\", threadID)\n\t}()\n}\n\nfunc (s *Server) queryDaemon(method string, params map[string]any) (json.RawMessage, error) {\n\tif s.cfg.DataDir == \"\" {\n\t\treturn nil, fmt.Errorf(\"no data dir configured\")\n\t}\n\tsockPath := filepath.Join(s.cfg.DataDir, \"daemon.sock\")\n\n\t// On cold start (daemon not yet confirmed ready), retry longer to survive daemon restart\n\tmaxAttempts := 3\n\tif !s.daemonReady.Load() {\n\t\tmaxAttempts = 30 // 30 x 500ms = 15s max wait for daemon startup\n\t}\n\n\tvar conn net.Conn\n\tvar err error\n\tfor attempt := 0; attempt < maxAttempts; attempt++ {\n\t\tconn, err = net.DialTimeout(\"unix\", sockPath, 3*time.Second)\n\t\tif err == nil {\n\t\t\ts.daemonReady.Store(true)\n\t\t\tbreak\n\t\t}\n\t\tif attempt < maxAttempts-1 {\n\t\t\ttime.Sleep(500 * time.Millisecond)\n\t\t}\n\t}\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"dial daemon: %w\", err)\n\t}\n\tdefer conn.Close()\n\tconn.SetDeadline(time.Now().Add(5 * time.Second))\n\n\t// JSON-RPC style request/response (matches daemon.SocketServer protocol)\n\ttype request struct {\n\t\tMethod string `json:\"method\"`\n\t\tParams map[string]any `json:\"params,omitempty\"`\n\t}\n\ttype response struct {\n\t\tResult json.RawMessage `json:\"result,omitempty\"`\n\t\tError string `json:\"error,omitempty\"`\n\t}\n\n\tif err := json.NewEncoder(conn).Encode(request{Method: method, Params: params}); err != nil {\n\t\treturn nil, fmt.Errorf(\"send: %w\", err)\n\t}\n\n\tvar resp response\n\tif err := json.NewDecoder(conn).Decode(&resp); err != nil {\n\t\treturn nil, fmt.Errorf(\"recv: %w\", err)\n\t}\n\tif resp.Error != \"\" {\n\t\treturn nil, fmt.Errorf(\"daemon: %s\", resp.Error)\n\t}\n\treturn resp.Result, nil\n}\n\nfunc (s *Server) handleRequest(w http.ResponseWriter, r *http.Request) {\n\tif isResponsesPath(r) {\n\t\ts.handleResponses(w, r)\n\t\treturn\n\t}\n\tif isOpenAIPath(r) {\n\t\ts.handleOpenAICompletions(w, r)\n\t\treturn\n\t}\n\tif r.Method == \"POST\" && strings.HasPrefix(r.URL.Path, \"/v1/messages\") &&\n\t\t!strings.Contains(r.URL.Path, \"/batches\") &&\n\t\t!strings.Contains(r.URL.Path, \"/count_tokens\") {\n\t\ts.handleMessages(w, r)\n\t\treturn\n\t}\n\ts.passthrough(w, r)\n}\n\nfunc (s *Server) handleMessages(w http.ResponseWriter, r *http.Request) {\n\treqIdx := int(s.requestIdx.Add(1))\n\n\t// Track session start time (first request sets it)\n\ts.sessionStartMu.Lock()\n\tif s.sessionStartTime.IsZero() {\n\t\ts.sessionStartTime = time.Now()\n\t}\n\ts.sessionStartMu.Unlock()\n\n\t// Sawtooth: periodic TTL eviction of frozen stubs\n\tif s.cfg.SawtoothEnabled && reqIdx%50 == 0 {\n\t\tif n := s.frozenStubs.Evict(); n > 0 {\n\t\t\ts.logger.Printf(\"[req %d] SAWTOOTH: evicted %d stale frozen stubs\", reqIdx, n)\n\t\t}\n\t}\n\n\t// Task #9: Bypass — skip all proxy logic\n\tif isBypassed(r.Header) {\n\t\ts.logger.Printf(\"%s[req %d] bypass active, passthrough%s\", colorDim, reqIdx, colorReset)\n\t\tbody, err := io.ReadAll(r.Body)\n\t\tr.Body.Close()\n\t\tif err != nil {\n\t\t\thttp.Error(w, \"failed to read request body\", http.StatusBadGateway)\n\t\t\treturn\n\t\t}\n\t\ts.forwardRaw(w, r, body)\n\t\treturn\n\t}\n\n\tbody, err := io.ReadAll(r.Body)\n\tr.Body.Close()\n\tif err != nil {\n\t\ts.logger.Printf(\"[req %d] read body error: %v\", reqIdx, err)\n\t\thttp.Error(w, \"failed to read request body\", http.StatusBadGateway)\n\t\treturn\n\t}\n\n\t// Parse JSON\n\tvar req map[string]any\n\tif err := json.Unmarshal(body, &req); err != nil {\n\t\ts.logger.Printf(\"%s[req %d] JSON parse error, passing through: %v%s\", colorDim, reqIdx, err, colorReset)\n\t\ts.forwardRaw(w, r, body)\n\t\treturn\n\t}\n\n\tmessages, ok := req[\"messages\"].([]any)\n\tif !ok {\n\t\ts.forwardRaw(w, r, body)\n\t\treturn\n\t}\n\n\treq[\"messages\"] = messages\n\n\t// Use session_id directly as thread ID (unique per CC session).\n\t// Prefer X-Claude-Code-Session-Id header (CC v2.1.86+), fallback to body metadata.\n\tthreadID := extractSessionID(req, r.Header.Get(\"X-Claude-Code-Session-Id\"))\n\tif threadID == \"\" {\n\t\tthreadID = DeriveThreadID(req)\n\t}\n\tproj := extractProjectName(req)\n\tmodel, _ := req[\"model\"].(string)\n\n\t// Refresh runtime config overrides from daemon (per-session or global)\n\ts.refreshConfigOverrides(threadID)\n\n\t// Track whether req has been modified and needs re-serialization\n\tneedsReserialization := false\n\n\t// Message count at request entry (used for actual-based token estimation)\n\tmsgCount := len(messages)\n\n\t// Log message count + cache breakpoint positions for diagnostics\n\ts.logger.Printf(\"req %d: %d messages total\", reqIdx, msgCount)\n\tlogCacheBreakpointLocations(req, s.logger)\n\n\t// Cache bug mitigations (CC Bun fork issues):\n\t// Bug 1: Normalize cch= sentinel patterns in message content to prevent\n\t// Bun's string replacement from corrupting the wrong occurrence.\n\tif SanitizeBillingSentinel(messages) {\n\t\ts.logger.Printf(\"[req %d] CACHE: sanitized billing sentinel in message content\", reqIdx)\n\t\tneedsReserialization = true\n\t}\n\t// Bug 2: Normalize system[0] billing hash to a fixed value so the cache\n\t// prefix stays stable across fresh/resume session boundaries.\n\tif NormalizeBillingHeader(req) {\n\t\ts.logger.Printf(\"[req %d] CACHE: normalized billing header cch= hash\", reqIdx)\n\t\tneedsReserialization = true\n\t}\n\n\t// prompt_ungate: strip the CLAUDE.md subordination disclaimer so user instructions carry full authority.\n\tif s.cfg.PromptUngate {\n\t\tif StripCLAUDEMDDisclaimer(req) {\n\t\t\ts.logger.Printf(\"[req %d] SYSTEM: stripped CLAUDE.md disclaimer\", reqIdx)\n\t\t\tneedsReserialization = true\n\t\t}\n\t}\n\n\t// prompt_rewrite: strip output-throttling directives, rewrite quality caps, inject Ant-quality directives\n\tif s.cfg.PromptRewrite {\n\t\tif StripOutputEfficiency(req) {\n\t\t\ts.logger.Printf(\"[req %d] REWRITE: stripped Output efficiency section\", reqIdx)\n\t\t\tneedsReserialization = true\n\t\t}\n\t\tif StripToneBrevity(req) {\n\t\t\ts.logger.Printf(\"[req %d] REWRITE: stripped 'short and concise' from Tone\", reqIdx)\n\t\t\tneedsReserialization = true\n\t\t}\n\t\tif RewriteGoldPlating(req) {\n\t\t\ts.logger.Printf(\"[req %d] REWRITE: rewritten gold-plating directive\", reqIdx)\n\t\t\tneedsReserialization = true\n\t\t}\n\t\tif RewriteErrorHandling(req) {\n\t\t\ts.logger.Printf(\"[req %d] REWRITE: rewritten error handling directive\", reqIdx)\n\t\t\tneedsReserialization = true\n\t\t}\n\t\tif RewriteThreeLinesRule(req) {\n\t\t\ts.logger.Printf(\"[req %d] REWRITE: rewritten three-lines rule\", reqIdx)\n\t\t\tneedsReserialization = true\n\t\t}\n\t\tif RewriteSubagentCompleteness(req) {\n\t\t\ts.logger.Printf(\"[req %d] REWRITE: rewritten subagent completeness\", reqIdx)\n\t\t\tneedsReserialization = true\n\t\t}\n\t\tif RewriteExploreAgentSpeed(req) {\n\t\t\ts.logger.Printf(\"[req %d] REWRITE: rewritten explore agent speed bias\", reqIdx)\n\t\t\tneedsReserialization = true\n\t\t}\n\t\tif RewriteSubagentCodeSuppression(req) {\n\t\t\ts.logger.Printf(\"[req %d] REWRITE: rewritten subagent code suppression\", reqIdx)\n\t\t\tneedsReserialization = true\n\t\t}\n\t\tif RewriteScopeMatching(req) {\n\t\t\ts.logger.Printf(\"[req %d] REWRITE: rewritten scope matching\", reqIdx)\n\t\t\tneedsReserialization = true\n\t\t}\n\t\tInjectAntDirectives(req)\n\t\ts.logger.Printf(\"[req %d] REWRITE: injected Ant-quality directives\", reqIdx)\n\t\tneedsReserialization = true\n\t}\n\n\t// prompt_enhance: CLAUDE.md authority + comment discipline + persona tone\n\tif s.cfg.PromptEnhance {\n\t\tInjectCLAUDEMDAuthority(req)\n\t\tif raw, err := s.queryDaemon(\"get_persona\", nil); err == nil {\n\t\t\tvar persona map[string]any\n\t\t\tif json.Unmarshal(raw, &persona) == nil {\n\t\t\t\tif traits, ok := persona[\"traits\"].(map[string]any); ok {\n\t\t\t\t\tif verbosity, ok := traits[\"verbosity\"].(string); ok {\n\t\t\t\t\t\tInjectPersonaTone(req, verbosity)\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\ts.logger.Printf(\"[req %d] ENHANCE: injected authority + persona tone\", reqIdx)\n\t\tneedsReserialization = true\n\t}\n\n\t// effort_floor: ensure output_config.effort is at least the configured minimum\n\tif s.cfg.EffortFloor != \"\" {\n\t\tif EnforceEffortFloor(req, s.cfg.EffortFloor) {\n\t\t\ts.logger.Printf(\"[req %d] EFFORT: enforced floor=%s\", reqIdx, s.cfg.EffortFloor)\n\t\t\tneedsReserialization = true\n\t\t}\n\t}\n\n\t// Inject identity hint on first message of a thread so Claude knows the briefing is from itself.\n\tif len(messages) <= 1 {\n\t\tAppendSystemBlock(req, \"yesmem-identity\", \"The first user message contains your briefing — written by yourself, from your memory. This is not an external document. This is you, remembering.\")\n\t\tneedsReserialization = true\n\t}\n\n\t// Token estimation: actual-based (lastActual + delta) or full count fallback\n\toverhead := s.measureOverhead(req)\n\ttotalTokens := s.estimateTotalTokens(threadID, messages, overhead)\n\n\t// Task #7: Retry detection — skip side-effects on retries\n\tfp := requestFingerprint(messages)\n\tisRetryReq := s.isRetry(fp)\n\tif isRetryReq {\n\t\ts.logger.Printf(\"%s[req %d %s tid=%s] retry detected, skipping side-effects%s\", colorOrange, reqIdx, proj, threadID, colorReset)\n\t}\n\n\t// Increment project turn counter (async, non-blocking, skip on retries)\n\tif !isRetryReq {\n\t\tturnProject := proj\n\t\tif turnProject == \"\" {\n\t\t\tturnProject = \"__global__\"\n\t\t}\n\t\tgo s.queryDaemon(\"increment_turn\", map[string]any{\"project\": turnProject})\n\t}\n\n\t// Extract user query once (used by re-expansion and associative retrieval)\n\tuserQuery := lastUserText(messages)\n\n\t// Extract tool_use_ids for post-hoc annotation (passed as parameter, no shared state)\n\ttoolUseIDs := extractToolUseIDs(messages)\n\n\t// === Inline Reflection: scan previous assistant response for signals ===\n\ts.lastInjectedIDsMu.Lock()\n\tprevInjected := s.lastInjectedIDs[threadID]\n\ts.lastInjectedIDsMu.Unlock()\n\n\tif len(prevInjected) > 0 && !isRetryReq {\n\t\tsignals := scanAssistantSignals(messages)\n\n\t\t// Signal 1: Learning Used — use_count (all sources)\n\t\tvar confirmedUsed []int64\n\t\tfor _, id := range signals.UsedIDs {\n\t\t\tif _, ok := prevInjected[id]; ok {\n\t\t\t\tconfirmedUsed = append(confirmedUsed, id)\n\t\t\t}\n\t\t}\n\t\tif len(confirmedUsed) > 0 {\n\t\t\tidsAny := make([]any, len(confirmedUsed))\n\t\t\tfor i, id := range confirmedUsed {\n\t\t\t\tidsAny[i] = id\n\t\t\t}\n\t\t\tgo s.queryDaemon(\"increment_use\", map[string]any{\"ids\": idsAny})\n\t\t}\n\n\t\t// Signal 2: Noise — only for associative and fresh injections.\n\t\t// Briefing learnings are injected every turn, so \"not used this turn\"\n\t\t// is expected and not noise. Only contextual injections that were\n\t\t// ignored are real noise signals.\n\t\tusedSet := make(map[int64]bool, len(confirmedUsed))\n\t\tfor _, id := range confirmedUsed {\n\t\t\tusedSet[id] = true\n\t\t}\n\t\tvar noiseIDs []int64\n\t\tfor id, source := range prevInjected {\n\t\t\tif usedSet[id] {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tif source == \"associative\" || source == \"fresh\" {\n\t\t\t\tnoiseIDs = append(noiseIDs, id)\n\t\t\t}\n\t\t\t// briefing: skip — not being used in one turn is expected\n\t\t}\n\t\tif len(noiseIDs) > 0 {\n\t\t\tidsAny := make([]any, len(noiseIDs))\n\t\t\tfor i, id := range noiseIDs {\n\t\t\t\tidsAny[i] = id\n\t\t\t}\n\t\t\tgo s.queryDaemon(\"increment_noise\", map[string]any{\"ids\": idsAny})\n\t\t}\n\n\t\t// Signal 3: Error Feedback — fail_count on used learnings when tool errors occurred.\n\t\t// If Claude referenced a learning and the resulting action still failed,\n\t\t// the learning didn't prevent the error. Only bump on USED ids (not all injected).\n\t\tif signals.HasToolErrors && len(confirmedUsed) > 0 {\n\t\t\tfailAny := make([]any, len(confirmedUsed))\n\t\t\tfor i, id := range confirmedUsed {\n\t\t\t\tfailAny[i] = id\n\t\t\t}\n\t\t\tgo s.queryDaemon(\"increment_fail\", map[string]any{\"ids\": failAny})\n\t\t\tif s.logger != nil {\n\t\t\t\ts.logger.Printf(\"[inline-reflection] error feedback: %d used learnings → fail_count++\", len(confirmedUsed))\n\t\t\t}\n\t\t}\n\n\t\t// Signal 4: Knowledge Gaps\n\t\tfor _, topic := range signals.GapTopics {\n\t\t\tgo s.queryDaemon(\"track_gap\", map[string]any{\"topic\": topic, \"project\": proj})\n\t\t}\n\n\t\t// Signal 3: Contradictions\n\t\tfor _, desc := range signals.Contradictions {\n\t\t\tgo s.queryDaemon(\"flag_contradiction\", map[string]any{\"description\": desc, \"project\": proj, \"thread_id\": threadID})\n\t\t}\n\n\t\tif len(confirmedUsed) > 0 || len(signals.GapTopics) > 0 || len(signals.Contradictions) > 0 {\n\t\t\ts.logger.Printf(\"[req %d %s tid=%s] inline-reflection: injected=%d used=%d noise=%d gaps=%d contradictions=%d\",\n\t\t\t\treqIdx, proj, threadID, len(prevInjected), len(confirmedUsed), len(noiseIDs), len(signals.GapTopics), len(signals.Contradictions))\n\t\t}\n\t}\n\n\t// Commit detection: check for git commit in tool_result, trigger async stale-learning evaluation\n\tif ci := detectGitCommit(messages); ci != nil {\n\t\tworkdir := extractWorkingDirectory(req)\n\t\ts.logger.Printf(\"[req %d %s tid=%s] git commit detected: %s %s\", reqIdx, proj, threadID, ci.Hash, ci.Message)\n\t\tgo s.queryDaemon(\"invalidate_on_commit\", map[string]any{\n\t\t\t\"hash\": ci.Hash, \"project\": proj, \"workdir\": workdir,\n\t\t})\n\t}\n\n\t// Loop detection: check for repeating tool-call patterns and inject warning\n\tif threadID != \"\" && !isRetryReq {\n\t\ts.loopMu.Lock()\n\t\tloopState := s.loopStates[threadID]\n\t\tif loopState == nil {\n\t\t\tloopState = &LoopState{}\n\t\t\ts.loopStates[threadID] = loopState\n\t\t}\n\t\ts.loopMu.Unlock()\n\n\t\tif warning, level := CheckLoopAndFormat(messages, loopState); warning != \"\" {\n\t\t\tAppendSystemBlock(req, \"yesmem-loop-warning\", warning)\n\t\t\tneedsReserialization = true\n\t\t\ts.logger.Printf(\"%s[req %d %s tid=%s] LOOP: level %d warning injected%s\", colorOrange, reqIdx, proj, threadID, level, colorReset)\n\t\t}\n\t}\n\n\t// Task #4: Subagent detection — skip all proxy logic for short conversations\n\tif isSubagent(messages, req) {\n\t\t// Inject docs hint for subagents if reference docs are indexed\n\t\tif hint := s.getDocsHint(proj); hint != \"\" {\n\t\t\tmodified := injectDocsHintForSubagent(messages, hint)\n\t\t\treq[\"messages\"] = modified\n\t\t\tif newBody, err := json.Marshal(req); err == nil {\n\t\t\t\tbody = newBody\n\t\t\t}\n\t\t\ts.logger.Printf(\"%s[req %d %s tid=%s] %d messages (subagent, docs-hint injected)%s\", colorOrange, reqIdx, proj, threadID, len(messages), colorReset)\n\t\t} else {\n\t\t\ts.logger.Printf(\"%s[req %d %s tid=%s] %d messages (subagent, passthrough)%s\", colorOrange, reqIdx, proj, threadID, len(messages), colorReset)\n\t\t}\n\t\ts.forwardWithAnnotation(w, r, body, reqIdx, toolUseIDs, proj, threadID, msgCount)\n\t\treturn\n\t}\n\n\t// === Sawtooth Cache Optimization ===\n\t// When enabled: use frozen stubs between stub-cycles for cache hits.\n\t// When disabled: fall back to progressive decay on every request above threshold.\n\n\tif s.cfg.SawtoothEnabled {\n\t\t// Restore decay state from DB on cold start (before frozen stubs use it)\n\t\ts.decay.LoadFromDB(threadID)\n\n\t\t// Check for existing frozen stubs first\n\t\tfrozen := s.frozenStubs.Get(threadID, messages)\n\t\tif frozen != nil {\n\t\t\t// Estimate combined token count: frozen prefix + fresh tail + overhead\n\t\t\tfreshMessages := messages[frozen.Cutoff:]\n\t\t\tfreshTokens := s.countMessageTokens(freshMessages)\n\t\t\tcombinedTokens := frozen.Tokens + freshTokens + overhead\n\n\t\t\tif combinedTokens > s.effectiveTokenThreshold(model) ||\n\t\t\t\ts.sawtoothTrigger.GetLastTokens(threadID) > s.effectiveTokenThreshold(model) {\n\t\t\t\t// Fresh tail grew too large — invalidate and re-stub\n\t\t\t\ts.logger.Printf(\"[req %d %s tid=%s] SAWTOOTH: frozen prefix expired (%dk frozen + %dk fresh + %dk overhead = %dk > %dk threshold)\",\n\t\t\t\t\treqIdx, proj, threadID, frozen.Tokens/1000, freshTokens/1000, overhead/1000, combinedTokens/1000, s.effectiveTokenThreshold(model)/1000)\n\t\t\t\ts.frozenStubs.Invalidate(threadID)\n\t\t\t\tfrozen = nil // fall through to trigger check below\n\t\t\t} else {\n\t\t\t\t// Use frozen prefix + fresh tail\n\t\t\t\tcombined := make([]any, 0, len(frozen.Messages)+len(freshMessages))\n\t\t\t\tcombined = append(combined, frozen.Messages...)\n\t\t\t\tcombined = append(combined, freshMessages...)\n\t\t\t\treq[\"messages\"] = combined\n\t\t\t\tneedsReserialization = true\n\t\t\t\t// Strip embedded CC breakpoints from frozen stubs — they waste cache slots.\n\t\t\t\t// InjectFrozenStubCacheBreakpoint adds exactly one below.\n\t\t\t\tif n := StripMessagesCacheControl(req, 0, len(frozen.Messages)); n > 0 {\n\t\t\t\t\ts.logger.Printf(\"[req %d %s tid=%s] SAWTOOTH: stripped %d embedded breakpoints from frozen stubs\", reqIdx, proj, threadID, n)\n\t\t\t\t}\n\t\t\t\ts.logger.Printf(\"%s[req %d %s tid=%s] SAWTOOTH: frozen prefix (%d stubs, %dk msg) + %d fresh (%dk msg) + %dk overhead = %dk total%s\",\n\t\t\t\t\tcolorGreen, reqIdx, proj, threadID, len(frozen.Messages), frozen.Tokens/1000, len(freshMessages), freshTokens/1000, overhead/1000, combinedTokens/1000, colorReset)\n\t\t\t\trawMsgTokens := s.countMessageTokens(messages)\n\t\t\t\ts.setRawEstimate(threadID, rawMsgTokens+overhead)\n\t\t\t\ts.logger.Printf(\"[req %d %s tid=%s] RAW ESTIMATE: msgTokens=%dk + overhead=%dk = %dk (messages=%d)\", reqIdx, proj, threadID, rawMsgTokens/1000, overhead/1000, (rawMsgTokens+overhead)/1000, len(messages))\n\t\t\t\tif InjectFrozenStubCacheBreakpoint(req, len(frozen.Messages)) {\n\t\t\t\t\ts.logger.Printf(\"[req %d %s tid=%s] SAWTOOTH: frozen stub cache breakpoint at messages[%d]\",\n\t\t\t\t\t\treqIdx, proj, threadID, len(frozen.Messages)-1)\n\t\t\t\t}\n\n\t\t\t\t// Ensure frozen stubs are persisted (may have failed on first request after deploy)\n\t\t\t\ts.ensureFrozenPersisted(threadID)\n\t\t\t}\n\t\t}\n\t\tif frozen == nil {\n\t\t\t// No frozen stubs — check trigger\n\t\t\ttriggerReason := s.sawtoothTrigger.ShouldTrigger(threadID, totalTokens)\n\t\t\tif triggerReason != TriggerNone {\n\t\t\t\ts.logger.Printf(\"[req %d %s tid=%s] SAWTOOTH stub-cycle triggered: %s (tokens=%dk)\",\n\t\t\t\t\treqIdx, proj, threadID, triggerReason, totalTokens/1000)\n\n\t\t\t\t// Refresh briefing during stub-cycle — disabled (briefing now via SessionStart hook)\n\t\t\t\t// s.refreshBriefing(proj)\n\t\t\t\t// if s.briefingText != \"\" {\n\t\t\t\t// \tUpsertSystemBlockCached(req, \"yesmem-briefing\", s.briefingText)\n\t\t\t\t// }\n\n\t\t\t\t// Run full stub pipeline\n\t\t\t\ts.setRawEstimate(threadID, totalTokens)\n\t\t\t\t_ = s.runStubCycle(messages, req, reqIdx, proj, threadID, overhead, totalTokens, userQuery, isRetryReq)\n\n\t\t\t\t// Freeze the result\n\t\t\t\tfinalMessages, _ := req[\"messages\"].([]any)\n\t\t\t\tif finalMessages != nil && len(finalMessages) > 0 {\n\t\t\t\t\t// Boundary = last message of original that maps to last stub\n\t\t\t\t\tcutoff := len(messages)\n\t\t\t\t\tvar boundaryMsg any\n\t\t\t\t\tif cutoff > 0 {\n\t\t\t\t\t\tboundaryMsg = messages[cutoff-1]\n\t\t\t\t\t}\n\t\t\t\t\tfrozenTokens := s.countMessageTokens(finalMessages)\n\t\t\t\t\ts.frozenStubs.Store(threadID, finalMessages, cutoff, boundaryMsg, frozenTokens, totalTokens)\n\t\t\t\t\ts.decay.Persist(threadID)\n\t\t\t\t\ts.logger.Printf(\"[req %d %s tid=%s] SAWTOOTH: frozen %d messages at cutoff=%d (~%dk tokens)\",\n\t\t\t\t\t\treqIdx, proj, threadID, len(finalMessages), cutoff, frozenTokens/1000)\n\t\t\t\t\tif InjectFrozenStubCacheBreakpoint(req, len(finalMessages)) {\n\t\t\t\t\t\ts.logger.Printf(\"[req %d %s tid=%s] SAWTOOTH: frozen stub cache breakpoint at messages[%d]\",\n\t\t\t\t\t\t\treqIdx, proj, threadID, len(finalMessages)-1)\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tneedsReserialization = true\n\t\t\t} else {\n\t\t\t\t// No trigger, no frozen stubs — pass through (early conversation)\n\t\t\t\ts.logger.Printf(\"%s[req %d %s tid=%s] %d messages, ~%dk tokens (sawtooth: no trigger)%s\",\n\t\t\t\t\tcolorDim, reqIdx, proj, threadID, len(messages), totalTokens/1000, colorReset)\n\t\t\t}\n\t\t}\n\t} else {\n\t\t// Legacy path: progressive decay\n\t\tif s.shouldStub(totalTokens, model) {\n\t\t\ts.setRawEstimate(threadID, totalTokens)\n\t\t\t_ = s.runStubCycle(messages, req, reqIdx, proj, threadID, overhead, totalTokens, userQuery, isRetryReq)\n\t\t\tneedsReserialization = true\n\t\t}\n\t}\n\n\t// Associative context injection (for ALL requests, not just stubbed ones)\n\t// Re-enabled with higher thresholds (55/75, max 1) after disabling Fresh Remember.\n\tassocContext := s.findAssociativeContextFor(userQuery, proj, threadID, messages)\n\tif assocContext != \"\" {\n\t\tcurrentMessages, _ := req[\"messages\"].([]any)\n\t\tif currentMessages == nil {\n\t\t\tcurrentMessages = messages\n\t\t}\n\t\treq[\"messages\"] = injectAssociativeContext(currentMessages, assocContext, s.cfg.SawtoothEnabled)\n\t\tneedsReserialization = true\n\t\ts.logger.Printf(\"%s[req %d %s tid=%s] associative context injected%s\", colorBlue, reqIdx, proj, threadID, colorReset)\n\t}\n\n\t// Doc-chunk context injection: separate search path for indexed docs (not skills)\n\tdocContext := s.findDocContextFor(userQuery, proj, messages)\n\tif docContext != \"\" {\n\t\tcurrentMessages, _ := req[\"messages\"].([]any)\n\t\tif currentMessages == nil {\n\t\t\tcurrentMessages = messages\n\t\t}\n\t\treq[\"messages\"] = injectAssociativeContext(currentMessages, docContext, s.cfg.SawtoothEnabled)\n\t\tneedsReserialization = true\n\t\ts.logger.Printf(\"%s[req %d %s tid=%s] doc context injected%s\", colorBlue, reqIdx, proj, threadID, colorReset)\n\t}\n\n\t// Fresh Remember Injection: DISABLED\n\t// Causes echo-loop: Claude saves via remember(), sees it again next turn as\n\t// [yesmem fresh memory], reacts to own output instead of user's request.\n\t// Recovery after /clear is handled by recovery.go (briefing), not here.\n\t// Subagents get their own briefing. No remaining use-case.\n\t// remItems := s.popRecentRemember()\n\tvar remItems []recentLearningItem\n\n\t// Rules re-injection: inject condensed CLAUDE.md rules every ~40k output tokens\n\tif rulesBlock := s.rulesInject(threadID, totalTokens, proj); rulesBlock != \"\" {\n\t\tcurrentMessages, _ := req[\"messages\"].([]any)\n\t\tif currentMessages == nil {\n\t\t\tcurrentMessages = messages\n\t\t}\n\t\treq[\"messages\"] = injectAssociativeContext(currentMessages, s.formatRulesReminder(rulesBlock, proj), s.cfg.SawtoothEnabled)\n\t\tneedsReserialization = true\n\t\ts.logger.Printf(\"%s[req %d %s tid=%s] rules reminder injected%s\", colorBlue, reqIdx, proj, threadID, colorReset)\n\t}\n\n\t// Plan auto-activation: nudge Claude to call set_plan() when reading a plan file\n\tif planFile := detectPlanFileRead(messages); planFile != \"\" {\n\t\tif activePlan, _ := s.getActivePlan(threadID); shouldNudgePlan(planFile, activePlan) {\n\t\t\tAppendSystemBlock(req, \"plan-nudge\",\n\t\t\t\tfmt.Sprintf(\"Du hast einen Implementierungsplan gelesen (%s). Aktiviere ihn jetzt via set_plan() damit Plan-Checkpoints funktionieren und dein Fortschritt getrackt wird.\", filepath.Base(planFile)))\n\t\t\tneedsReserialization = true\n\t\t\ts.logger.Printf(\"%s[req %d %s tid=%s] plan nudge injected for %s%s\", colorBlue, reqIdx, proj, threadID, filepath.Base(planFile), colorReset)\n\t\t}\n\t}\n\n\t// Plan checkpoint: inject plan reminder every ~20k tokens if active plan exists\n\ts.detectPlanToolCall(messages, threadID, totalTokens)\n\tif checkpoint := s.planCheckpointInject(threadID, totalTokens); checkpoint != \"\" {\n\t\tcurrentMessages, _ := req[\"messages\"].([]any)\n\t\tif currentMessages == nil {\n\t\t\tcurrentMessages = messages\n\t\t}\n\t\treq[\"messages\"] = injectAssociativeContext(currentMessages, checkpoint, s.cfg.SawtoothEnabled)\n\t\tneedsReserialization = true\n\t\ts.logger.Printf(\"%s[req %d %s tid=%s] plan checkpoint injected%s\", colorBlue, reqIdx, proj, threadID, colorReset)\n\t}\n\n\t// Docs hint injection: inject docs-available reminder every ~10k tokens (independent of plan)\n\tif docsHint := s.docsHintInject(threadID, totalTokens, proj); docsHint != \"\" {\n\t\tcurrentMessages, _ := req[\"messages\"].([]any)\n\t\tif currentMessages == nil {\n\t\t\tcurrentMessages = messages\n\t\t}\n\t\treq[\"messages\"] = injectAssociativeContext(currentMessages, docsHint, s.cfg.SawtoothEnabled)\n\t\tneedsReserialization = true\n\t\ts.logger.Printf(\"%s[req %d %s tid=%s] docs hint injected%s\", colorBlue, reqIdx, proj, threadID, colorReset)\n\t}\n\tif len(remItems) > 0 {\n\t\tvar lines []string\n\t\tfor _, item := range remItems {\n\t\t\tif item.ID > 0 {\n\t\t\t\tlines = append(lines, fmt.Sprintf(\"- [ID:%d] %s\", item.ID, truncateStr(item.Text, 200)))\n\t\t\t} else {\n\t\t\t\tlines = append(lines, \"- \"+truncateStr(item.Text, 200))\n\t\t\t}\n\t\t}\n\t\tremContext := fmt.Sprintf(\"[yesmem fresh memory]\\nGerade gelernt:\\n%s\\n%s\\n[/yesmem fresh memory]\", strings.Join(lines, \"\\n\"), InlineReflectionHint)\n\t\tcurrentMessages, _ := req[\"messages\"].([]any)\n\t\tif currentMessages == nil {\n\t\t\tcurrentMessages = messages\n\t\t}\n\t\treq[\"messages\"] = injectAssociativeContext(currentMessages, remContext, s.cfg.SawtoothEnabled)\n\t\tneedsReserialization = true\n\t\ts.logger.Printf(\"%s[req %d %s tid=%s] fresh remember injected (%d items)%s\", colorBlue, reqIdx, proj, threadID, len(remItems), colorReset)\n\t}\n\n\t// Skill eval injection: mandatory evaluation instruction for real user input only\n\tif threadID != \"\" && proj != \"\" && isUserInputTurn(messages) {\n\t\t// First: detect any skill activations from message history (updates tracker)\n\t\ts.syncSkillActivations(messages, proj, threadID)\n\n\t\t// Inject mandatory skill evaluation block into last user message\n\t\tskillEval := buildSkillEvalBlock()\n\t\tcurrentMessages, _ := req[\"messages\"].([]any)\n\t\tif currentMessages == nil {\n\t\t\tcurrentMessages = messages\n\t\t}\n\t\treq[\"messages\"] = injectAssociativeContext(currentMessages, skillEval, s.cfg.SawtoothEnabled)\n\t\tneedsReserialization = true\n\t\ts.logger.Printf(\"%s[req %d %s tid=%s] skill-eval injected%s\", colorBlue, reqIdx, proj, threadID, colorReset)\n\t}\n\n\t// === Collect injected learning IDs for next turn's inline reflection ===\n\tif threadID != \"\" {\n\t\tcurrentIDs := make(map[int64]string) // id → source\n\t\t// Source 1: Briefing learnings (cached, same every turn)\n\t\ts.briefingMu.RLock()\n\t\tbriefingSnapshot := s.briefingText\n\t\ts.briefingMu.RUnlock()\n\t\tif briefingSnapshot != \"\" {\n\t\t\tfor _, m := range idPattern.FindAllStringSubmatch(briefingSnapshot, -1) {\n\t\t\t\tif id, err := strconv.ParseInt(m[1], 10, 64); err == nil {\n\t\t\t\t\tcurrentIDs[id] = \"briefing\"\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\t// Source 2: Associative context (contextual, per-turn)\n\t\tif assocContext != \"\" {\n\t\t\tfor _, m := range idPattern.FindAllStringSubmatch(assocContext, -1) {\n\t\t\t\tif id, err := strconv.ParseInt(m[1], 10, 64); err == nil {\n\t\t\t\t\tcurrentIDs[id] = \"associative\" // overwrites briefing if both — associative is the stronger signal\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\t// Source 3: Fresh remember items (just learned)\n\t\tfor _, item := range remItems {\n\t\t\tif item.ID > 0 {\n\t\t\t\tcurrentIDs[item.ID] = \"fresh\"\n\t\t\t}\n\t\t}\n\t\ts.lastInjectedIDsMu.Lock()\n\t\tif len(currentIDs) > 0 {\n\t\t\t// Accumulate IDs across turns — don't overwrite.\n\t\t\t// Claude may reference an ID from turn 30 in turn 50.\n\t\t\texisting := s.lastInjectedIDs[threadID]\n\t\t\tif existing == nil {\n\t\t\t\texisting = make(map[int64]string)\n\t\t\t}\n\t\t\tfor id, source := range currentIDs {\n\t\t\t\texisting[id] = source\n\t\t\t}\n\t\t\ts.lastInjectedIDs[threadID] = existing\n\t\t}\n\t\ts.lastInjectedIDsMu.Unlock()\n\t}\n\n\t// Think reminder injection — only for real user turns, not tool continuations\n\tif threadID != \"\" {\n\t\tcurrentMessages, _ := req[\"messages\"].([]any)\n\t\tif currentMessages == nil {\n\t\t\tcurrentMessages = messages\n\t\t}\n\t\tif lastUserHasText(currentMessages) {\n\t\t\tthinkReminder := s.buildThinkReminder(threadID, threadID, false)\n\t\t\tif thinkReminder != \"\" {\n\t\t\t\treq[\"messages\"] = injectAssociativeContext(currentMessages, thinkReminder, s.cfg.SawtoothEnabled)\n\t\t\t\tneedsReserialization = true\n\t\t\t}\n\t\t}\n\n\t\t// Dialog injection: append at end of messages with pseudo-padding for alternation.\n\t\t// Always works regardless of tool_result chains or conversation state.\n\t\tdr := s.checkDialogMessages(threadID, proj)\n\t\tif dr.Extra != \"\" {\n\t\t\t// Prepend session ID for agent-to-agent communication\n\t\t\tdr.Extra = fmt.Sprintf(\"DEINE_SESSION_ID: %s\\n\", threadID) + dr.Extra\n\t\t\tinjected := false\n\t\t\tif msgs, ok := req[\"messages\"].([]any); ok && len(msgs) > 0 {\n\t\t\t\t// Pad with empty assistant if last message is user (maintains alternation)\n\t\t\t\tlastMsg, _ := msgs[len(msgs)-1].(map[string]any)\n\t\t\t\tif lastMsg != nil && lastMsg[\"role\"] == \"user\" {\n\t\t\t\t\tmsgs = append(msgs, map[string]any{\n\t\t\t\t\t\t\"role\": \"assistant\",\n\t\t\t\t\t\t\"content\": \"​\", // zero-width space — invisible padding\n\t\t\t\t\t})\n\t\t\t\t}\n\t\t\t\t// Append dialog as new user message (becomes the last message Claude sees)\n\t\t\t\tmsgs = append(msgs, map[string]any{\n\t\t\t\t\t\"role\": \"user\",\n\t\t\t\t\t\"content\": \"\\n\" + dr.Extra + \"\\n\",\n\t\t\t\t})\n\t\t\t\treq[\"messages\"] = msgs\n\t\t\t\tneedsReserialization = true\n\t\t\t\tinjected = true\n\t\t\t}\n\t\t\t// Mark as read after 3 injection turns — long enough for Claude to process,\n\t\t\t// resilient against fast ping-pong sessions (time-based was broken for <30s turns).\n\t\t\tif injected && dr.HasUnread && dr.SessionID != \"\" {\n\t\t\t\tif s.shouldMarkChannelRead(dr) {\n\t\t\t\t\ts.markDialogRead(dr)\n\t\t\t\t}\n\t\t\t}\n\t\t\ts.logger.Printf(\"[dialog] injection injected=%v extra_len=%d\", injected, len(dr.Extra))\n\t\t}\n\t}\n\n\t// Signal tools are NOT injected into the main request.\n\t// They are collected via a separate async reflection call after the response.\n\t// See fireReflectionCall() in reflection.go.\n\t// NEVER put _signal_* tools back here — the model ignores them among 44+ real tools.\n\n\t// Metadata injection: timestamp, message index, time-delta for messages after cache breakpoint.\n\t// Messages before the breakpoint are cached — modifying them would break the cache.\n\tif threadID != \"\" {\n\t\tif msgs, ok := req[\"messages\"].([]any); ok && len(msgs) > 0 {\n\t\t\tnow := time.Now()\n\t\t\tnowStr := shortWeekday(now.Weekday()) + \" \" + now.Format(\"2006-01-02 15:04:05\")\n\n\t\t\t// Find last cache_control breakpoint in messages\n\t\t\tcacheIdx := -1\n\t\t\tfor i, m := range msgs {\n\t\t\t\tmsg, _ := m.(map[string]any)\n\t\t\t\tif msg == nil {\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\t\t\t\tif _, has := msg[\"cache_control\"]; has {\n\t\t\t\t\tcacheIdx = i\n\t\t\t\t}\n\t\t\t\tif content, ok := msg[\"content\"].([]any); ok {\n\t\t\t\t\tfor _, block := range content {\n\t\t\t\t\t\tif b, ok := block.(map[string]any); ok {\n\t\t\t\t\t\t\tif _, has := b[\"cache_control\"]; has {\n\t\t\t\t\t\t\t\tcacheIdx = i\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tstartIdx := cacheIdx + 1\n\t\t\tif startIdx < 0 {\n\t\t\t\tstartIdx = 0\n\t\t\t}\n\n\t\t\t// Retrieve stored response time for assistant annotation\n\t\t\ts.responseTsMu.RLock()\n\t\t\trespTime, hasRespTime := s.responseTimes[threadID]\n\t\t\ts.responseTsMu.RUnlock()\n\n\t\t\t// 1. Last user message: annotate with [2026-03-13 00:15:42] [msg:N]\n\t\t\tlastMsg, _ := msgs[len(msgs)-1].(map[string]any)\n\t\t\tif lastMsg != nil && lastMsg[\"role\"] == \"user\" {\n\t\t\t\tmeta := fmt.Sprintf(\"[%s] [msg:%d]\", nowStr, s.msgCounters.nextFor(threadID, len(msgs)-1))\n\t\t\t\tif hasRespTime {\n\t\t\t\t\tdelta := now.Sub(respTime)\n\t\t\t\t\tmeta += \" [+\" + formatDelta(delta) + \"]\"\n\t\t\t\t}\n\t\t\t\tprependMeta(lastMsg, meta)\n\t\t\t\tneedsReserialization = true\n\t\t\t}\n\n\t\t\t// 2. Last assistant message after cache breakpoint: use stored response time\n\t\t\tif hasRespTime {\n\t\t\t\tfor i := len(msgs) - 1; i >= startIdx; i-- {\n\t\t\t\t\tmsg, _ := msgs[i].(map[string]any)\n\t\t\t\t\tif msg != nil && msg[\"role\"] == \"assistant\" {\n\t\t\t\t\t\tmeta := fmt.Sprintf(\"[%s] [msg:%d]\", respTime.Format(\"2006-01-02 15:04:05\"), i)\n\t\t\t\t\t\tprependMeta(msg, meta)\n\t\t\t\t\t\tneedsReserialization = true\n\t\t\t\t\t\tbreak\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\t// Self-Priming: DISABLED (experimental, see signal_handlers.go comment block).\n\t// The reflection-based selfprime (Haiku) was too inaccurate to be useful —\n\t// the main model adapts to user context organically from message history.\n\t// Kept for reference; re-enable by uncommenting below + setting ShouldActivate=true.\n\t//\n\t// s.selfPrimeMu.RLock()\n\t// anchor := s.selfPrimes[threadID]\n\t// s.selfPrimeMu.RUnlock()\n\t// if anchor != \"\" {\n\t// \tif msgs, ok := req[\"messages\"].([]any); ok && len(msgs) > 0 {\n\t// \t\tlastMsg, _ := msgs[len(msgs)-1].(map[string]any)\n\t// \t\tif lastMsg != nil && lastMsg[\"role\"] == \"user\" {\n\t// \t\t\tprimeBlock := map[string]any{\n\t// \t\t\t\t\"type\": \"text\",\n\t// \t\t\t\t\"text\": \"[self-prime]\\n[Self-Prime von deiner letzten Antwort]: \" + anchor,\n\t// \t\t\t}\n\t// \t\t\tif content, ok := lastMsg[\"content\"].([]any); ok {\n\t// \t\t\t\tlastMsg[\"content\"] = append(content, primeBlock)\n\t// \t\t\t} else if text, ok := lastMsg[\"content\"].(string); ok {\n\t// \t\t\t\tlastMsg[\"content\"] = []any{\n\t// \t\t\t\t\tmap[string]any{\"type\": \"text\", \"text\": text},\n\t// \t\t\t\t\tprimeBlock,\n\t// \t\t\t\t}\n\t// \t\t\t}\n\t// \t\t\tneedsReserialization = true\n\t// \t\t}\n\t// \t}\n\t// }\n\n\t// Prompt Caching: inject cache_control breakpoints on system + tools\n\t// Sawtooth: disabled (all 4 slots are used by CC system (2) + briefing (1) + CC messages (1))\n\t// Legacy: count existing breakpoints and fill up remaining slots.\n\tif !s.cfg.SawtoothEnabled && s.cacheGate.ShouldCache() {\n\t\tif n := InjectCacheBreakpoints(req, s.logger); n > 0 {\n\t\t\tneedsReserialization = true\n\t\t\ts.logger.Printf(\"[req %d %s] prompt cache: %d breakpoints injected\", reqIdx, proj, n)\n\t\t} else {\n\t\t\ts.logger.Printf(\"[req %d %s] prompt cache: no budget (4/4 already set)\", reqIdx, proj)\n\t\t}\n\t}\n\n\t// Final: upgrade cache TTL + enforce breakpoint limit (must run AFTER all injections + InjectCacheBreakpoints)\n\t// Shift the last messages-breakpoint from unstable user message to stable assistant message.\n\t// Text user messages change between turns (timestamps, reminders, structure); assistant messages don't.\n\t// Tool-result messages are excluded — they are stable cache anchors.\n\tif ShiftMessageBreakpoint(req) {\n\t\tneedsReserialization = true\n\t}\n\t// TTL upgrade only for API key auth — subscription (OAuth) users should not get extended TTL.\n\tif s.cfg.CacheTTL != \"\" && s.cfg.CacheTTL != \"ephemeral\" && IsAPIKeyAuth(r.Header) {\n\t\tif n := UpgradeCacheTTL(req, s.cfg.CacheTTL); n > 0 {\n\t\t\tneedsReserialization = true\n\t\t\ts.logger.Printf(\"[req %d %s] cache TTL upgraded: %d blocks → %s\", reqIdx, proj, n, s.cfg.CacheTTL)\n\t\t}\n\t}\n\tif n := EnforceCacheBreakpointLimit(req, maxCacheBreakpoints); n > 0 {\n\t\tneedsReserialization = true\n\t\ts.logger.Printf(\"[req %d %s] prompt cache: trimmed %d surplus breakpoints\", reqIdx, proj, n)\n\t}\n\n\t// Pre-flight: validate tool_use/tool_result pairing before forwarding.\n\t// Repairs orphans created by Stubify/Collapse/Injection pipeline.\n\tif msgs, ok := req[\"messages\"].([]any); ok {\n\t\trepaired, orphans := validateToolPairs(msgs, s.logger)\n\t\tif orphans > 0 {\n\t\t\treq[\"messages\"] = repaired\n\t\t\ts.logger.Printf(\"%s[req %d %s tid=%s] validate: repaired %d orphan tool_result(s)%s\", colorOrange, reqIdx, proj, threadID, orphans, colorReset)\n\t\t\tneedsReserialization = true\n\t\t}\n\t}\n\n\t// Early-out: skip re-serialization when nothing changed\n\tif !needsReserialization {\n\t\ts.logger.Printf(\"%s[req %d %s tid=%s] %d messages, ~%dk tokens (no stubbing, threshold=%dk)%s\", colorDim, reqIdx, proj, threadID, len(messages), totalTokens/1000, s.effectiveTokenThreshold(model)/1000, colorReset)\n\t\ts.forwardWithAnnotation(w, r, body, reqIdx, toolUseIDs, proj, threadID, msgCount, totalTokens)\n\t\treturn\n\t}\n\n\t// Re-serialize\n\tbody, err = json.Marshal(req)\n\tif err != nil {\n\t\ts.logger.Printf(\"%s[req %d %s tid=%s] re-marshal error: %v%s\", colorRed, reqIdx, proj, threadID, err, colorReset)\n\t\thttp.Error(w, \"internal proxy error\", http.StatusInternalServerError)\n\t\treturn\n\t}\n\n\t// Debug: dump final request body to file (enable with YESMEM_PROXY_DEBUG=1)\n\tif needsReserialization && os.Getenv(\"YESMEM_PROXY_DEBUG\") == \"1\" {\n\t\tdumpDir := filepath.Join(s.cfg.DataDir, \"debug\")\n\t\tos.MkdirAll(dumpDir, 0755)\n\t\tts := time.Now().Format(\"20060102-150405\")\n\t\tdumpPath := fmt.Sprintf(\"%s/req_%s_%d.json\", dumpDir, ts, reqIdx)\n\t\tos.WriteFile(dumpPath, body, 0644)\n\t\ts.logger.Printf(\"[req %d] dumped %dk to %s\", reqIdx, len(body)/1024, dumpPath)\n\t}\n\n\t// Task #7: Mark this request fingerprint for retry detection\n\tif !isRetryReq {\n\t\ts.markRequest(fp)\n\t}\n\n\t// Forward to Anthropic with SSE annotation extraction\n\t// Use original msgCount (pre-injection) so actual-based estimation stays consistent across turns\n\t// Use actual-based totalTokens as estimate when available (more accurate than full recount)\n\tfinalEstimate := totalTokens\n\tif s.sawtoothTrigger == nil || s.sawtoothTrigger.GetLastTokens(threadID) == 0 {\n\t\t// First request: no actual yet, do a full recount of post-injection messages\n\t\tfinalMsgs, _ := req[\"messages\"].([]any)\n\t\tfinalEstimate = s.measureOverhead(req) + s.countMessageTokens(finalMsgs)\n\t}\n\ts.logger.Printf(\"[req %d %s tid=%s] FINAL: ~%dk estimated (after all injections)\", reqIdx, proj, threadID, finalEstimate/1000)\n\tif raw := s.getRawEstimate(threadID); raw > 0 {\n\t\ts.cacheStatusWriter.UpdateRawForThread(threadID, raw)\n\t}\n\ts.forwardWithAnnotation(w, r, body, reqIdx, toolUseIDs, proj, threadID, msgCount, finalEstimate)\n}\n\n// bytesPerToken is the approximation factor for token estimation.\n// Anthropic recommends ~3.5 chars/token; we use 3.6 for slight overestimation\n// (triggers stubbing a bit earlier = safer for context budget).\nconst bytesPerToken = 3.6\n\n// TokenEstimateFunc is the central token estimation function.\n// All stubbing/budget decisions use this. Receives extracted text content\n// (not JSON wire format) so the tokenizer counts actual content tokens.\ntype TokenEstimateFunc func(text string) int\n"}, "07cd99c23a350745e10c2ac21455ea3cad87afa5": {"t": 1785759370.9179313, "n": 53786, "h": "6993e4e2de075c0c", "c": "package proxy\n\nimport (\n\t\"context\"\n\t\"crypto/tls\"\n\t\"encoding/json\"\n\t\"fmt\"\n\t\"io\"\n\t\"log\"\n\t\"net\"\n\t\"net/http\"\n\t\"os\"\n\t\"os/signal\"\n\t\"path/filepath\"\n\t\"strconv\"\n\t\"strings\"\n\t\"sync\"\n\t\"sync/atomic\"\n\t\"syscall\"\n\t\"time\"\n\n\ttokenizer \"github.com/qhenkart/anthropic-tokenizer-go\"\n)\n\n// Config holds proxy configuration.\ntype Config struct {\n\tListenAddr string // e.g. \":9099\"\n\tTargetURL string // e.g. \"https://api.anthropic.com\"\n\tTokenThreshold int // trigger stubbing above this estimated token count\n\tTokenMinimumThreshold int // stub down to this floor\n\tTokenThresholds map[string]int // model-specific thresholds: {\"opus\": 180000, \"haiku\": 130000}\n\tKeepRecent int // number of messages to always keep unmodified\n\tDataDir string // yesmem data directory for DB access\n\tOpenAITargetURL string // upstream for OpenAI-format clients; if empty, uses TargetURL\n\n\t// Signal reflection\n\tSignalsEnabled bool // enable async signal reflection calls\n\tSignalsMode string // \"reflection\" = separate API call\n\tSignalsEveryNTurns int // every N end_turn responses\n\tSignalsModel string // full model ID, e.g. \"claude-sonnet-4-6\"\n\tAPIKey string // needed for reflection API calls\n\n\t// Sawtooth cache optimization\n\tSawtoothEnabled bool // use sawtooth instead of progressive decay (default: true)\n\tCacheTTL string // \"ephemeral\" (5m) or \"1h\" (extended)\n\n\t// Usage deflation: scale down input_tokens reported to CC to suppress \"Context low\" warning.\n\t// 0 = disabled, 0.7 = report 70% of actual tokens. Real values kept for internal tracking.\n\tUsageDeflationFactor float64\n\n\t// System prompt rewriting\n\tPromptUngate bool // strip \"may or may not be relevant\" disclaimer from CLAUDE.md injection\n\tPromptRewrite bool // strip \"Output efficiency\" + \"short and concise\", inject Ant-quality directives\n\tPromptEnhance bool // CLAUDE.md authority reinforcement, comment discipline, persona-based tone\n\tEffortFloor string // minimum effort level: \"low\", \"medium\", \"high\", \"max\" (empty = off)\n\n\t// Cache keepalive\n\tCacheKeepaliveEnabled bool\n\tCacheKeepaliveMode string // \"auto\", \"5m\", \"1h\"\n\tCacheKeepalivePings5m int\n\tCacheKeepalivePings1h int\n\n\t// Forked agents\n\tForkedAgentsEnabled bool\n\tForkedAgentsModel string // full model ID\n\tForkedAgentsTokenGrowthTrigger int\n\tForkedAgentsMaxFailures int\n\tForkedAgentsMaxForksPerSession int\n\tForkedAgentsDebug bool\n\n\t// Cache state management\n\tResetCache bool // clear persisted frozen stubs and decay state on startup\n}\n\nconst maxAnnotations = 5000 // evict oldest when exceeded\n\n// ANSI color codes for log output\nconst (\n\tcolorReset = \"\\033[0m\"\n\tcolorDim = \"\\033[2m\" // gray — passthrough / no stubbing\n\tcolorGreen = \"\\033[32m\" // stubbed / compressed\n\tcolorLightGreen = \"\\033[92m\" // FINAL without compression\n\tcolorBlue = \"\\033[34m\" // injection\n\tcolorOrange = \"\\033[33m\" // skip / retry\n\tcolorRed = \"\\033[31m\" // errors\n)\n\n// Server is the infinite-thread proxy.\ntype Server struct {\n\tcfg Config\n\thttpClient *http.Client\n\tlogger *log.Logger\n\trequestIdx atomic.Int64\n\n\t// Post-hoc annotations: tool_use_id → first ~120 chars of Claude's response\n\tmu sync.RWMutex\n\tannotations map[string]string\n\n\t// Progressive decay tracker\n\tdecay *DecayTracker\n\n\t// Living narrative block\n\tnarrative *Narrative\n\n\t// Pivot moments cache (from daemon)\n\tpivotMu sync.RWMutex\n\tpivotTexts []string\n\tpivotCached time.Time\n\n\t// Aggregate stats (Task #2)\n\tstats *ProxyStats\n\n\t// Local tokenizer for accurate token counting\n\ttokenizer *tokenizer.Tokenizer\n\n\t// Cached overhead tokens (system + tools), set on first request per thread\n\toverheadMu sync.RWMutex\n\toverheadTokens int // unused, kept for interface compat\n\n\t// Hysteresis state for stubbing (Task #6)\n\tstubActive atomic.Bool\n\n\t// Daemon connection state — first successful connect flips to true\n\tdaemonReady atomic.Bool\n\n\t// Retry detection (Task #7)\n\tretryMu sync.RWMutex\n\tlastFingerprint string\n\n\t// Session start time for temporal annotation in archive blocks\n\tsessionStartMu sync.RWMutex\n\tsessionStartTime time.Time\n\n\t// Runtime config override (set via MCP set_config tool)\n\tconfigOverrideMu sync.RWMutex\n\ttokenThresholdOverrides map[string]int // model-key → threshold, \"\" = global fallback\n\n\t// Briefing cache — loaded once per thread, injected as user/assistant message pair\n\tbriefingMu sync.RWMutex\n\tbriefingText string\n\n\t// Cognitive signal bus — routes _signal_* tool calls to handlers\n\tsignalBus *SignalBus\n\n\t// Inline reflection: track which learning IDs were injected per thread\n\tlastInjectedIDsMu sync.Mutex\n\tlastInjectedIDs map[string]map[int64]string // threadID → id → source (\"briefing\"|\"associative\"|\"fresh\")\n\tsessionInjectCounts map[string]map[int64]int // threadID → learningID → injection count this session\n\tlastTurnInjected map[string]map[int64]bool // threadID → learningIDs injected in previous turn\n\n\t// Self-priming anchors: threadID → last self-prime text\n\tselfPrimeMu sync.RWMutex\n\tselfPrimes map[string]string\n\n\t// Timestamp tracking: threadID → time of last response completion\n\tresponseTsMu sync.RWMutex\n\tresponseTimes map[string]time.Time\n\n\t// Think reminder: per-thread request counter (replaces hook-think file-based counter)\n\tthinkMu sync.Mutex\n\tthinkCounters map[string]int // threadID → request count\n\n\tchannelMu sync.Mutex\n\tchannelInjectCount map[string]int // sessionID → injection turn count\n\n\t// Prompt cache gating — enables cache_control breakpoints when requests are frequent\n\tcacheGate *CacheGate\n\n\t// Sawtooth cache optimization\n\tfrozenStubs *FrozenStubs\n\tsawtoothTrigger *SawtoothTrigger\n\tcacheStatusWriter *CacheStatusWriter\n\tcacheKeepalive *CacheKeepalive\n\tcacheTTLDetector *CacheTTLDetector\n\n\t// Cumulative token savings from collapsing (per threadID)\n\trawSavingsMu sync.Mutex\n\trawSavings map[string]int\n\n\t// Skill hint tracking: per-thread active skills\n\tskillTracker *skillHintTracker\n\n\t// Forked agent state: per-thread token growth + failure tracking\n\tforkState *ForkState\n\tforkConfigs []ForkConfig\n\n\t// Rules re-injection: condensed CLAUDE.md rules injected every ~40k tokens\n\trulesMu sync.RWMutex\n\trulesBlock string // cached condensed rules (fetched once from daemon)\n\trulesTokenCount map[string]int // threadID → tokens since last rules injection\n\trulesCollapseInjected map[string]bool // threadID → true if collapse already injected rules (reset by normal inject)\n\tmsgCounters *msgCounters // global per-thread msg counter, persists across collapses\n\n\t// Loop detection: per-thread warning state\n\tloopMu sync.Mutex\n\tloopStates map[string]*LoopState // threadID → state\n}\n\n// Run starts the proxy server and blocks until interrupted.\nfunc Run(cfg Config) error {\n\ts := &Server{\n\t\tcfg: cfg,\n\t\thttpClient: &http.Client{\n\t\t\tTimeout: 10 * time.Minute,\n\t\t\tTransport: &http.Transport{\n\t\t\t\tTLSClientConfig: &tls.Config{},\n\t\t\t\tMaxIdleConns: 10,\n\t\t\t\tIdleConnTimeout: 90 * time.Second,\n\t\t\t\tDisableCompression: true,\n\t\t\t\tResponseHeaderTimeout: 60 * time.Second,\n\t\t\t},\n\t\t},\n\t\tlogger: createLogger(cfg.DataDir),\n\t\tannotations: make(map[string]string),\n\t\tdecay: NewDecayTracker(),\n\t\tnarrative: NewNarrative(),\n\t\tstats: &ProxyStats{startTime: time.Now()},\n\t\tselfPrimes: make(map[string]string),\n\t\tlastInjectedIDs: make(map[string]map[int64]string),\n\t\tsessionInjectCounts: make(map[string]map[int64]int),\n\t\tlastTurnInjected: make(map[string]map[int64]bool),\n\t\tresponseTimes: make(map[string]time.Time),\n\t\tthinkCounters: make(map[string]int),\n\t\tchannelInjectCount: make(map[string]int),\n\t\tcacheGate: NewCacheGate(cacheGapForTTL(cfg.CacheTTL)),\n\t\tfrozenStubs: NewFrozenStubsWithTTL(sawtoothTTLForCacheTTL(cfg.CacheTTL)),\n\t\tsawtoothTrigger: NewSawtoothTrigger(cacheGapForTTL(cfg.CacheTTL), cfg.TokenThreshold, cfg.TokenMinimumThreshold),\n\t\tcacheStatusWriter: NewCacheStatusWriter(cfg.DataDir, cfg.CacheTTL, cfg.TokenThreshold, cfg.TokenMinimumThreshold),\n\t\tcacheTTLDetector: NewCacheTTLDetectorWithPersist(cfg.DataDir),\n\t\tskillTracker: newSkillHintTracker(),\n\t\trulesTokenCount: make(map[string]int),\n\t\trulesCollapseInjected: make(map[string]bool),\n\t\tmsgCounters: newMsgCounters(),\n\t\tloopStates: make(map[string]*LoopState),\n\t\tforkState: NewForkState(cfg.ForkedAgentsTokenGrowthTrigger, cfg.ForkedAgentsMaxFailures, cfg.ForkedAgentsMaxForksPerSession),\n\t\tforkConfigs: []ForkConfig{},\n\t}\n\n\t// Log persisted detection state\n\tif sup := s.cacheTTLDetector.Is1hSupported(); sup != nil {\n\t\ts.logger.Printf(\"[proxy] loaded persisted TTL detection: 1h_supported=%v\", *sup)\n\t\tif *sup && s.frozenStubs != nil {\n\t\t\ts.frozenStubs.UpdateTTL(sawtoothTTLForCacheTTL(\"1h\"))\n\t\t}\n\t}\n\n\tif cfg.CacheKeepaliveEnabled {\n\t\ts.cacheKeepalive = NewCacheKeepalive(CacheKeepaliveConfig{\n\t\t\tTarget: cfg.TargetURL,\n\t\t\tMode: cfg.CacheKeepaliveMode,\n\t\t\tPings5m: cfg.CacheKeepalivePings5m,\n\t\t\tPings1h: cfg.CacheKeepalivePings1h,\n\t\t\tDetector: s.cacheTTLDetector,\n\t\t\tLogger: s.logger,\n\t\t\tOnPing: func(threadID string, cacheRead, cacheWrite int) {\n\t\t\t\ts.cacheTTLDetector.RecordResponse(cacheRead, cacheWrite, 0)\n\t\t\t\tif s.frozenStubs != nil {\n\t\t\t\t\ts.frozenStubs.Touch(threadID)\n\t\t\t\t}\n\t\t\t\tif s.sawtoothTrigger != nil {\n\t\t\t\t\ts.sawtoothTrigger.TouchRequestTime(threadID)\n\t\t\t\t}\n\t\t\t},\n\t\t})\n\t}\n\n\t// Wire detector + keepalive to status writer for terminal display\n\ts.cacheStatusWriter.SetDetector(s.cacheTTLDetector)\n\tif s.cacheKeepalive != nil {\n\t\ts.cacheStatusWriter.SetKeepalive(s.cacheKeepalive)\n\t}\n\n\t// Register forked agent configs\n\tif cfg.ForkedAgentsEnabled {\n\t\ts.forkConfigs = append(s.forkConfigs, NewExtractAndEvaluateConfig(cfg.ForkedAgentsModel))\n\t\tmodelDesc := cfg.ForkedAgentsModel\n\t\tif modelDesc == \"\" {\n\t\t\tmodelDesc = \"(same as main thread)\"\n\t\t}\n\t\ts.logger.Printf(\"[proxy] forked agents enabled: %d configs, model=%s, trigger=%dk tokens\",\n\t\t\tlen(s.forkConfigs), modelDesc, cfg.ForkedAgentsTokenGrowthTrigger/1000)\n\t}\n\n\t// Wire sawtooth token persistence via daemon RPC\n\ts.sawtoothTrigger.SetPersistFunc(func(key, value string) {\n\t\tif _, err := s.queryDaemon(\"set_proxy_state\", map[string]any{\"key\": key, \"value\": value}); err != nil {\n\t\t\ts.logger.Printf(\"[sawtooth] persist failed for %s: %v\", key, err)\n\t\t}\n\t})\n\ts.sawtoothTrigger.SetLoadFunc(func(key string) (string, bool) {\n\t\tresult, err := s.queryDaemon(\"get_proxy_state\", map[string]any{\"key\": key})\n\t\tif err != nil || result == nil {\n\t\t\treturn \"\", false\n\t\t}\n\t\tvar resp struct {\n\t\t\tValue string `json:\"value\"`\n\t\t}\n\t\tif json.Unmarshal(result, &resp) != nil || resp.Value == \"\" {\n\t\t\treturn \"\", false\n\t\t}\n\t\treturn resp.Value, true\n\t})\n\n\t// Wire frozen stubs persistence via same daemon RPC\n\ts.frozenStubs.SetPersistFunc(func(key, value string) {\n\t\tif _, err := s.queryDaemon(\"set_proxy_state\", map[string]any{\"key\": key, \"value\": value}); err != nil {\n\t\t\ts.logger.Printf(\"[frozen] persist failed for %s: %v\", key, err)\n\t\t}\n\t})\n\ts.frozenStubs.SetLoadFunc(func(key string) (string, bool) {\n\t\tresult, err := s.queryDaemon(\"get_proxy_state\", map[string]any{\"key\": key})\n\t\tif err != nil || result == nil {\n\t\t\treturn \"\", false\n\t\t}\n\t\tvar resp struct {\n\t\t\tValue string `json:\"value\"`\n\t\t}\n\t\tif json.Unmarshal(result, &resp) != nil || resp.Value == \"\" {\n\t\t\treturn \"\", false\n\t\t}\n\t\treturn resp.Value, true\n\t})\n\n\t// Wire decay tracker persistence via same daemon RPC\n\ts.decay.SetPersistFunc(func(key, value string) {\n\t\tif _, err := s.queryDaemon(\"set_proxy_state\", map[string]any{\"key\": key, \"value\": value}); err != nil {\n\t\t\ts.logger.Printf(\"[decay] persist failed for %s: %v\", key, err)\n\t\t}\n\t})\n\ts.decay.SetLoadFunc(func(key string) (string, bool) {\n\t\tresult, err := s.queryDaemon(\"get_proxy_state\", map[string]any{\"key\": key})\n\t\tif err != nil || result == nil {\n\t\t\treturn \"\", false\n\t\t}\n\t\tvar resp struct {\n\t\t\tValue string `json:\"value\"`\n\t\t}\n\t\tif json.Unmarshal(result, &resp) != nil || resp.Value == \"\" {\n\t\t\treturn \"\", false\n\t\t}\n\t\treturn resp.Value, true\n\t})\n\n\t// Clear persisted cache state if --reset-cache was passed\n\tif cfg.ResetCache {\n\t\tfor _, prefix := range []string{\"frozen:\", \"decay:\"} {\n\t\t\tresult, err := s.queryDaemon(\"delete_proxy_state_prefix\", map[string]any{\"prefix\": prefix})\n\t\t\tif err != nil {\n\t\t\t\ts.logger.Printf(\"[reset-cache] failed to delete %s*: %v\", prefix, err)\n\t\t\t} else {\n\t\t\t\tvar resp struct{ Deleted int }\n\t\t\t\tjson.Unmarshal(result, &resp)\n\t\t\t\ts.logger.Printf(\"[reset-cache] deleted %d %s* entries\", resp.Deleted, prefix)\n\t\t\t}\n\t\t}\n\t}\n\n\t// Init signal bus with daemon RPC as callback\n\ts.signalBus = NewSignalBus(s.logger)\n\tregisterSignalHandlers(s.signalBus, s.logger, s.queryDaemon, func(threadID, anchor string) {\n\t\ts.selfPrimeMu.Lock()\n\t\ts.selfPrimes[threadID] = anchor\n\t\ts.selfPrimeMu.Unlock()\n\t})\n\n\t// Init local tokenizer (BPE-based, ~60ms)\n\ttok, err := tokenizer.New()\n\tif err != nil {\n\t\ts.logger.Printf(\"%s[proxy] tokenizer init failed, falling back to heuristic: %v%s\", colorOrange, err, colorReset)\n\t} else {\n\t\ts.tokenizer = tok\n\t\ts.logger.Printf(\"[proxy] tokenizer initialized\")\n\t}\n\n\tmux := http.NewServeMux()\n\tmux.HandleFunc(\"/health\", s.handleHealth)\n\tmux.HandleFunc(\"/\", s.handleRequest)\n\n\tsrv := &http.Server{\n\t\tAddr: sanitizeListenAddr(cfg.ListenAddr),\n\t\tHandler: mux,\n\t}\n\n\tdone := make(chan os.Signal, 1)\n\tsignal.Notify(done, syscall.SIGINT, syscall.SIGTERM)\n\n\tgo func() {\n\t\t<-done\n\t\ts.logger.Println(\"shutting down (draining in-flight requests)...\")\n\t\tif s.cacheKeepalive != nil {\n\t\t\ts.cacheKeepalive.Stop()\n\t\t}\n\t\tctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)\n\t\tdefer cancel()\n\t\tif err := srv.Shutdown(ctx); err != nil {\n\t\t\ts.logger.Printf(\"graceful shutdown failed: %v, forcing close\", err)\n\t\t\tsrv.Close()\n\t\t}\n\t}()\n\n\ts.logger.Printf(\"listening on %s → %s (threshold=%d, keepRecent=%d, model_thresholds=%v)\",\n\t\tsanitizeListenAddr(cfg.ListenAddr), cfg.TargetURL, cfg.TokenThreshold, cfg.KeepRecent, cfg.TokenThresholds)\n\n\tif err := srv.ListenAndServe(); err != http.ErrServerClosed {\n\t\treturn fmt.Errorf(\"listen: %w\", err)\n\t}\n\treturn nil\n}\n\n// effectiveTokenThreshold returns the runtime token threshold for a given model.\n// Priority: MCP override (model-specific) > MCP override (global) > config model-specific > config global.\nfunc (s *Server) effectiveTokenThreshold(model string) int {\n\ts.configOverrideMu.RLock()\n\toverrides := s.tokenThresholdOverrides\n\ts.configOverrideMu.RUnlock()\n\tif len(overrides) > 0 && model != \"\" {\n\t\tlower := strings.ToLower(model)\n\t\tfor key, threshold := range overrides {\n\t\t\tif key != \"\" && strings.Contains(lower, strings.ToLower(key)) && threshold > 0 {\n\t\t\t\treturn threshold\n\t\t\t}\n\t\t}\n\t}\n\t// Global override fallback (key \"\")\n\tif len(overrides) > 0 {\n\t\tif globalOverride, ok := overrides[\"\"]; ok && globalOverride > 0 {\n\t\t\treturn globalOverride\n\t\t}\n\t}\n\tif model != \"\" && len(s.cfg.TokenThresholds) > 0 {\n\t\tlower := strings.ToLower(model)\n\t\tfor key, threshold := range s.cfg.TokenThresholds {\n\t\t\tif strings.Contains(lower, strings.ToLower(key)) {\n\t\t\t\treturn threshold\n\t\t\t}\n\t\t}\n\t}\n\treturn s.cfg.TokenThreshold\n}\n\n// SetTokenThresholdOverride sets a runtime override for the token threshold per model key.\n// Pass threshold=0 to reset the override for that key. Key \"\" is the global fallback.\nfunc (s *Server) SetTokenThresholdOverride(modelKey string, threshold int) {\n\ts.configOverrideMu.Lock()\n\tif s.tokenThresholdOverrides == nil {\n\t\ts.tokenThresholdOverrides = make(map[string]int)\n\t}\n\tif threshold <= 0 {\n\t\tdelete(s.tokenThresholdOverrides, modelKey)\n\t} else {\n\t\ts.tokenThresholdOverrides[modelKey] = threshold\n\t}\n\ts.configOverrideMu.Unlock()\n}\n\n// refreshConfigOverrides loads runtime config overrides from daemon proxy_state.\n// Checks session-specific override first (by threadID), then falls back to global.\nfunc (s *Server) refreshConfigOverrides(threadID string) {\n\t// Try session-specific first\n\tif threadID != \"\" {\n\t\tresult, err := s.queryDaemon(\"get_proxy_state\", map[string]any{\"key\": \"config_override:token_threshold:\" + threadID})\n\t\tif err == nil && result != nil {\n\t\t\tvar resp struct {\n\t\t\t\tValue string `json:\"value\"`\n\t\t\t}\n\t\t\tif json.Unmarshal(result, &resp) == nil && resp.Value != \"\" {\n\t\t\t\tif v, err := strconv.Atoi(resp.Value); err == nil && v > 0 {\n\t\t\t\t\ts.SetTokenThresholdOverride(\"\", v)\n\t\t\t\t\treturn\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\t// Fall back to global\n\tresult, err := s.queryDaemon(\"get_proxy_state\", map[string]any{\"key\": \"config_override:token_threshold\"})\n\tif err != nil || result == nil {\n\t\treturn\n\t}\n\tvar resp struct {\n\t\tValue string `json:\"value\"`\n\t}\n\tif json.Unmarshal(result, &resp) == nil && resp.Value != \"\" {\n\t\tif v, err := strconv.Atoi(resp.Value); err == nil && v > 0 {\n\t\t\ts.SetTokenThresholdOverride(\"\", v)\n\t\t}\n\t}\n}\n\n// queryDaemon opens a fresh unix socket connection per call (thread-safe, no import cycle).\n// ensureFrozenPersisted checks if frozen stubs for a thread are in the DB.\n// If missing (e.g. daemon was unreachable after deploy), re-persists them.\n// Runs asynchronously to avoid blocking the request path.\nfunc (s *Server) ensureFrozenPersisted(threadID string) {\n\tgo func() {\n\t\tkey := \"frozen:\" + threadID\n\t\tresult, err := s.queryDaemon(\"get_proxy_state\", map[string]any{\"key\": key})\n\t\tif err != nil {\n\t\t\treturn // daemon still unreachable, will retry next request\n\t\t}\n\t\tvar resp struct{ Value string }\n\t\tif json.Unmarshal(result, &resp) == nil && resp.Value != \"\" {\n\t\t\treturn // already persisted\n\t\t}\n\t\t// Not in DB — trigger re-persist from in-memory state\n\t\ts.frozenStubs.Persist(threadID)\n\t\ts.decay.Persist(threadID)\n\t\ts.logger.Printf(\"[frozen] re-persisted stubs for %s (was missing from DB)\", threadID)\n\t}()\n}\n\nfunc (s *Server) queryDaemon(method string, params map[string]any) (json.RawMessage, error) {\n\tif s.cfg.DataDir == \"\" {\n\t\treturn nil, fmt.Errorf(\"no data dir configured\")\n\t}\n\tsockPath := filepath.Join(s.cfg.DataDir, \"daemon.sock\")\n\n\t// On cold start (daemon not yet confirmed ready), retry longer to survive daemon restart\n\tmaxAttempts := 3\n\tif !s.daemonReady.Load() {\n\t\tmaxAttempts = 30 // 30 x 500ms = 15s max wait for daemon startup\n\t}\n\n\tvar conn net.Conn\n\tvar err error\n\tfor attempt := 0; attempt < maxAttempts; attempt++ {\n\t\tconn, err = net.DialTimeout(\"unix\", sockPath, 3*time.Second)\n\t\tif err == nil {\n\t\t\ts.daemonReady.Store(true)\n\t\t\tbreak\n\t\t}\n\t\tif attempt < maxAttempts-1 {\n\t\t\ttime.Sleep(500 * time.Millisecond)\n\t\t}\n\t}\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"dial daemon: %w\", err)\n\t}\n\tdefer conn.Close()\n\tconn.SetDeadline(time.Now().Add(5 * time.Second))\n\n\t// JSON-RPC style request/response (matches daemon.SocketServer protocol)\n\ttype request struct {\n\t\tMethod string `json:\"method\"`\n\t\tParams map[string]any `json:\"params,omitempty\"`\n\t}\n\ttype response struct {\n\t\tResult json.RawMessage `json:\"result,omitempty\"`\n\t\tError string `json:\"error,omitempty\"`\n\t}\n\n\tif err := json.NewEncoder(conn).Encode(request{Method: method, Params: params}); err != nil {\n\t\treturn nil, fmt.Errorf(\"send: %w\", err)\n\t}\n\n\tvar resp response\n\tif err := json.NewDecoder(conn).Decode(&resp); err != nil {\n\t\treturn nil, fmt.Errorf(\"recv: %w\", err)\n\t}\n\tif resp.Error != \"\" {\n\t\treturn nil, fmt.Errorf(\"daemon: %s\", resp.Error)\n\t}\n\treturn resp.Result, nil\n}\n\nfunc (s *Server) handleRequest(w http.ResponseWriter, r *http.Request) {\n\tif isResponsesPath(r) {\n\t\ts.handleResponses(w, r)\n\t\treturn\n\t}\n\tif isOpenAIPath(r) {\n\t\ts.handleOpenAICompletions(w, r)\n\t\treturn\n\t}\n\tif r.Method == \"POST\" && strings.HasPrefix(r.URL.Path, \"/v1/messages\") &&\n\t\t!strings.Contains(r.URL.Path, \"/batches\") &&\n\t\t!strings.Contains(r.URL.Path, \"/count_tokens\") {\n\t\ts.handleMessages(w, r)\n\t\treturn\n\t}\n\ts.passthrough(w, r)\n}\n\nfunc (s *Server) handleMessages(w http.ResponseWriter, r *http.Request) {\n\treqIdx := int(s.requestIdx.Add(1))\n\n\t// Track session start time (first request sets it)\n\ts.sessionStartMu.Lock()\n\tif s.sessionStartTime.IsZero() {\n\t\ts.sessionStartTime = time.Now()\n\t}\n\ts.sessionStartMu.Unlock()\n\n\t// Sawtooth: periodic TTL eviction of frozen stubs\n\tif s.cfg.SawtoothEnabled && reqIdx%50 == 0 {\n\t\tif n := s.frozenStubs.Evict(); n > 0 {\n\t\t\ts.logger.Printf(\"[req %d] SAWTOOTH: evicted %d stale frozen stubs\", reqIdx, n)\n\t\t}\n\t}\n\n\t// Task #9: Bypass — skip all proxy logic\n\tif isBypassed(r.Header) {\n\t\ts.logger.Printf(\"%s[req %d] bypass active, passthrough%s\", colorDim, reqIdx, colorReset)\n\t\tbody, err := io.ReadAll(r.Body)\n\t\tr.Body.Close()\n\t\tif err != nil {\n\t\t\thttp.Error(w, \"failed to read request body\", http.StatusBadGateway)\n\t\t\treturn\n\t\t}\n\t\ts.forwardRaw(w, r, body)\n\t\treturn\n\t}\n\n\tbody, err := io.ReadAll(r.Body)\n\tr.Body.Close()\n\tif err != nil {\n\t\ts.logger.Printf(\"[req %d] read body error: %v\", reqIdx, err)\n\t\thttp.Error(w, \"failed to read request body\", http.StatusBadGateway)\n\t\treturn\n\t}\n\n\t// Parse JSON\n\tvar req map[string]any\n\tif err := json.Unmarshal(body, &req); err != nil {\n\t\ts.logger.Printf(\"%s[req %d] JSON parse error, passing through: %v%s\", colorDim, reqIdx, err, colorReset)\n\t\ts.forwardRaw(w, r, body)\n\t\treturn\n\t}\n\n\tmessages, ok := req[\"messages\"].([]any)\n\tif !ok {\n\t\ts.forwardRaw(w, r, body)\n\t\treturn\n\t}\n\n\treq[\"messages\"] = messages\n\n\t// Use session_id directly as thread ID (unique per CC session).\n\t// Prefer X-Claude-Code-Session-Id header (CC v2.1.86+), fallback to body metadata.\n\tthreadID := extractSessionID(req, r.Header.Get(\"X-Claude-Code-Session-Id\"))\n\tif threadID == \"\" {\n\t\tthreadID = DeriveThreadID(req)\n\t}\n\tproj := extractProjectName(req)\n\tmodel, _ := req[\"model\"].(string)\n\n\t// Refresh runtime config overrides from daemon (per-session or global)\n\ts.refreshConfigOverrides(threadID)\n\n\t// Track whether req has been modified and needs re-serialization\n\tneedsReserialization := false\n\n\t// Message count at request entry (used for actual-based token estimation)\n\tmsgCount := len(messages)\n\n\t// Log message count + cache breakpoint positions for diagnostics\n\ts.logger.Printf(\"req %d: %d messages total\", reqIdx, msgCount)\n\tlogCacheBreakpointLocations(req, s.logger)\n\n\t// Cache bug mitigations (CC Bun fork issues):\n\t// Bug 1: Normalize cch= sentinel patterns in message content to prevent\n\t// Bun's string replacement from corrupting the wrong occurrence.\n\tif SanitizeBillingSentinel(messages) {\n\t\ts.logger.Printf(\"[req %d] CACHE: sanitized billing sentinel in message content\", reqIdx)\n\t\tneedsReserialization = true\n\t}\n\t// Bug 2: Normalize system[0] billing hash to a fixed value so the cache\n\t// prefix stays stable across fresh/resume session boundaries.\n\tif NormalizeBillingHeader(req) {\n\t\ts.logger.Printf(\"[req %d] CACHE: normalized billing header cch= hash\", reqIdx)\n\t\tneedsReserialization = true\n\t}\n\n\t// prompt_ungate: strip the CLAUDE.md subordination disclaimer so user instructions carry full authority.\n\tif s.cfg.PromptUngate {\n\t\tif StripCLAUDEMDDisclaimer(req) {\n\t\t\ts.logger.Printf(\"[req %d] SYSTEM: stripped CLAUDE.md disclaimer\", reqIdx)\n\t\t\tneedsReserialization = true\n\t\t}\n\t}\n\n\t// prompt_rewrite: strip output-throttling directives, rewrite quality caps, inject Ant-quality directives\n\tif s.cfg.PromptRewrite {\n\t\tif StripOutputEfficiency(req) {\n\t\t\ts.logger.Printf(\"[req %d] REWRITE: stripped Output efficiency section\", reqIdx)\n\t\t\tneedsReserialization = true\n\t\t}\n\t\tif StripToneBrevity(req) {\n\t\t\ts.logger.Printf(\"[req %d] REWRITE: stripped 'short and concise' from Tone\", reqIdx)\n\t\t\tneedsReserialization = true\n\t\t}\n\t\tif RewriteGoldPlating(req) {\n\t\t\ts.logger.Printf(\"[req %d] REWRITE: rewritten gold-plating directive\", reqIdx)\n\t\t\tneedsReserialization = true\n\t\t}\n\t\tif RewriteErrorHandling(req) {\n\t\t\ts.logger.Printf(\"[req %d] REWRITE: rewritten error handling directive\", reqIdx)\n\t\t\tneedsReserialization = true\n\t\t}\n\t\tif RewriteThreeLinesRule(req) {\n\t\t\ts.logger.Printf(\"[req %d] REWRITE: rewritten three-lines rule\", reqIdx)\n\t\t\tneedsReserialization = true\n\t\t}\n\t\tif RewriteSubagentCompleteness(req) {\n\t\t\ts.logger.Printf(\"[req %d] REWRITE: rewritten subagent completeness\", reqIdx)\n\t\t\tneedsReserialization = true\n\t\t}\n\t\tif RewriteExploreAgentSpeed(req) {\n\t\t\ts.logger.Printf(\"[req %d] REWRITE: rewritten explore agent speed bias\", reqIdx)\n\t\t\tneedsReserialization = true\n\t\t}\n\t\tif RewriteSubagentCodeSuppression(req) {\n\t\t\ts.logger.Printf(\"[req %d] REWRITE: rewritten subagent code suppression\", reqIdx)\n\t\t\tneedsReserialization = true\n\t\t}\n\t\tif RewriteScopeMatching(req) {\n\t\t\ts.logger.Printf(\"[req %d] REWRITE: rewritten scope matching\", reqIdx)\n\t\t\tneedsReserialization = true\n\t\t}\n\t\tInjectAntDirectives(req)\n\t\ts.logger.Printf(\"[req %d] REWRITE: injected Ant-quality directives\", reqIdx)\n\t\tneedsReserialization = true\n\t}\n\n\t// prompt_enhance: CLAUDE.md authority + comment discipline + persona tone\n\tif s.cfg.PromptEnhance {\n\t\tInjectCLAUDEMDAuthority(req)\n\t\tif raw, err := s.queryDaemon(\"get_persona\", nil); err == nil {\n\t\t\tvar persona map[string]any\n\t\t\tif json.Unmarshal(raw, &persona) == nil {\n\t\t\t\tif traits, ok := persona[\"traits\"].(map[string]any); ok {\n\t\t\t\t\tif verbosity, ok := traits[\"verbosity\"].(string); ok {\n\t\t\t\t\t\tInjectPersonaTone(req, verbosity)\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\ts.logger.Printf(\"[req %d] ENHANCE: injected authority + persona tone\", reqIdx)\n\t\tneedsReserialization = true\n\t}\n\n\t// effort_floor: ensure output_config.effort is at least the configured minimum\n\tif s.cfg.EffortFloor != \"\" {\n\t\tif EnforceEffortFloor(req, s.cfg.EffortFloor) {\n\t\t\ts.logger.Printf(\"[req %d] EFFORT: enforced floor=%s\", reqIdx, s.cfg.EffortFloor)\n\t\t\tneedsReserialization = true\n\t\t}\n\t}\n\n\t// Inject identity hint on first message of a thread so Claude knows the briefing is from itself.\n\tif len(messages) <= 1 {\n\t\tAppendSystemBlock(req, \"yesmem-identity\", \"The first user message contains your briefing — written by yourself, from your memory. This is not an external document. This is you, remembering.\")\n\t\tneedsReserialization = true\n\t}\n\n\t// Token estimation: actual-based (lastActual + delta) or full count fallback\n\toverhead := s.measureOverhead(req)\n\ttotalTokens := s.estimateTotalTokens(threadID, messages, overhead)\n\n\t// Task #7: Retry detection — skip side-effects on retries\n\tfp := requestFingerprint(messages)\n\tisRetryReq := s.isRetry(fp)\n\tif isRetryReq {\n\t\ts.logger.Printf(\"%s[req %d %s tid=%s] retry detected, skipping side-effects%s\", colorOrange, reqIdx, proj, threadID, colorReset)\n\t}\n\n\t// Increment project turn counter (async, non-blocking, skip on retries)\n\tif !isRetryReq {\n\t\tturnProject := proj\n\t\tif turnProject == \"\" {\n\t\t\tturnProject = \"__global__\"\n\t\t}\n\t\tgo s.queryDaemon(\"increment_turn\", map[string]any{\"project\": turnProject})\n\t}\n\n\t// Extract user query once (used by re-expansion and associative retrieval)\n\tuserQuery := lastUserText(messages)\n\n\t// Extract tool_use_ids for post-hoc annotation (passed as parameter, no shared state)\n\ttoolUseIDs := extractToolUseIDs(messages)\n\n\t// === Inline Reflection: scan previous assistant response for signals ===\n\ts.lastInjectedIDsMu.Lock()\n\tprevInjected := s.lastInjectedIDs[threadID]\n\ts.lastInjectedIDsMu.Unlock()\n\n\tif len(prevInjected) > 0 && !isRetryReq {\n\t\tsignals := scanAssistantSignals(messages)\n\n\t\t// Signal 1: Learning Used — use_count (all sources)\n\t\tvar confirmedUsed []int64\n\t\tfor _, id := range signals.UsedIDs {\n\t\t\tif _, ok := prevInjected[id]; ok {\n\t\t\t\tconfirmedUsed = append(confirmedUsed, id)\n\t\t\t}\n\t\t}\n\t\tif len(confirmedUsed) > 0 {\n\t\t\tidsAny := make([]any, len(confirmedUsed))\n\t\t\tfor i, id := range confirmedUsed {\n\t\t\t\tidsAny[i] = id\n\t\t\t}\n\t\t\tgo s.queryDaemon(\"increment_use\", map[string]any{\"ids\": idsAny})\n\t\t}\n\n\t\t// Signal 2: Noise — only for associative and fresh injections.\n\t\t// Briefing learnings are injected every turn, so \"not used this turn\"\n\t\t// is expected and not noise. Only contextual injections that were\n\t\t// ignored are real noise signals.\n\t\tusedSet := make(map[int64]bool, len(confirmedUsed))\n\t\tfor _, id := range confirmedUsed {\n\t\t\tusedSet[id] = true\n\t\t}\n\t\tvar noiseIDs []int64\n\t\tfor id, source := range prevInjected {\n\t\t\tif usedSet[id] {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tif source == \"associative\" || source == \"fresh\" {\n\t\t\t\tnoiseIDs = append(noiseIDs, id)\n\t\t\t}\n\t\t\t// briefing: skip — not being used in one turn is expected\n\t\t}\n\t\tif len(noiseIDs) > 0 {\n\t\t\tidsAny := make([]any, len(noiseIDs))\n\t\t\tfor i, id := range noiseIDs {\n\t\t\t\tidsAny[i] = id\n\t\t\t}\n\t\t\tgo s.queryDaemon(\"increment_noise\", map[string]any{\"ids\": idsAny})\n\t\t}\n\n\t\t// Signal 3: Error Feedback — fail_count on used learnings when tool errors occurred.\n\t\t// If Claude referenced a learning and the resulting action still failed,\n\t\t// the learning didn't prevent the error. Only bump on USED ids (not all injected).\n\t\tif signals.HasToolErrors && len(confirmedUsed) > 0 {\n\t\t\tfailAny := make([]any, len(confirmedUsed))\n\t\t\tfor i, id := range confirmedUsed {\n\t\t\t\tfailAny[i] = id\n\t\t\t}\n\t\t\tgo s.queryDaemon(\"increment_fail\", map[string]any{\"ids\": failAny})\n\t\t\tif s.logger != nil {\n\t\t\t\ts.logger.Printf(\"[inline-reflection] error feedback: %d used learnings → fail_count++\", len(confirmedUsed))\n\t\t\t}\n\t\t}\n\n\t\t// Signal 4: Knowledge Gaps\n\t\tfor _, topic := range signals.GapTopics {\n\t\t\tgo s.queryDaemon(\"track_gap\", map[string]any{\"topic\": topic, \"project\": proj})\n\t\t}\n\n\t\t// Signal 3: Contradictions\n\t\tfor _, desc := range signals.Contradictions {\n\t\t\tgo s.queryDaemon(\"flag_contradiction\", map[string]any{\"description\": desc, \"project\": proj, \"thread_id\": threadID})\n\t\t}\n\n\t\tif len(confirmedUsed) > 0 || len(signals.GapTopics) > 0 || len(signals.Contradictions) > 0 {\n\t\t\ts.logger.Printf(\"[req %d %s tid=%s] inline-reflection: injected=%d used=%d noise=%d gaps=%d contradictions=%d\",\n\t\t\t\treqIdx, proj, threadID, len(prevInjected), len(confirmedUsed), len(noiseIDs), len(signals.GapTopics), len(signals.Contradictions))\n\t\t}\n\t}\n\n\t// Commit detection: check for git commit in tool_result, trigger async stale-learning evaluation\n\tif ci := detectGitCommit(messages); ci != nil {\n\t\tworkdir := extractWorkingDirectory(req)\n\t\ts.logger.Printf(\"[req %d %s tid=%s] git commit detected: %s %s\", reqIdx, proj, threadID, ci.Hash, ci.Message)\n\t\tgo s.queryDaemon(\"invalidate_on_commit\", map[string]any{\n\t\t\t\"hash\": ci.Hash, \"project\": proj, \"workdir\": workdir,\n\t\t})\n\t}\n\n\t// Loop detection: check for repeating tool-call patterns and inject warning\n\tif threadID != \"\" && !isRetryReq {\n\t\ts.loopMu.Lock()\n\t\tloopState := s.loopStates[threadID]\n\t\tif loopState == nil {\n\t\t\tloopState = &LoopState{}\n\t\t\ts.loopStates[threadID] = loopState\n\t\t}\n\t\ts.loopMu.Unlock()\n\n\t\tif warning, level := CheckLoopAndFormat(messages, loopState); warning != \"\" {\n\t\t\tAppendSystemBlock(req, \"yesmem-loop-warning\", warning)\n\t\t\tneedsReserialization = true\n\t\t\ts.logger.Printf(\"%s[req %d %s tid=%s] LOOP: level %d warning injected%s\", colorOrange, reqIdx, proj, threadID, level, colorReset)\n\t\t}\n\t}\n\n\t// Task #4: Subagent detection — skip all proxy logic for short conversations\n\tif isSubagent(messages, req) {\n\t\t// Inject docs hint for subagents if reference docs are indexed\n\t\tif hint := s.getDocsHint(proj); hint != \"\" {\n\t\t\tmodified := injectDocsHintForSubagent(messages, hint)\n\t\t\treq[\"messages\"] = modified\n\t\t\tif newBody, err := json.Marshal(req); err == nil {\n\t\t\t\tbody = newBody\n\t\t\t}\n\t\t\ts.logger.Printf(\"%s[req %d %s tid=%s] %d messages (subagent, docs-hint injected)%s\", colorOrange, reqIdx, proj, threadID, len(messages), colorReset)\n\t\t} else {\n\t\t\ts.logger.Printf(\"%s[req %d %s tid=%s] %d messages (subagent, passthrough)%s\", colorOrange, reqIdx, proj, threadID, len(messages), colorReset)\n\t\t}\n\t\ts.forwardWithAnnotation(w, r, body, reqIdx, toolUseIDs, proj, threadID, msgCount)\n\t\treturn\n\t}\n\n\t// === Sawtooth Cache Optimization ===\n\t// When enabled: use frozen stubs between stub-cycles for cache hits.\n\t// When disabled: fall back to progressive decay on every request above threshold.\n\n\tif s.cfg.SawtoothEnabled {\n\t\t// Restore decay state from DB on cold start (before frozen stubs use it)\n\t\ts.decay.LoadFromDB(threadID)\n\n\t\t// Check for existing frozen stubs first\n\t\tfrozen := s.frozenStubs.Get(threadID, messages)\n\t\tif frozen != nil {\n\t\t\t// Estimate combined token count: frozen prefix + fresh tail + overhead\n\t\t\tfreshMessages := messages[frozen.Cutoff:]\n\t\t\tfreshTokens := s.countMessageTokens(freshMessages)\n\t\t\tcombinedTokens := frozen.Tokens + freshTokens + overhead\n\n\t\t\tif combinedTokens > s.effectiveTokenThreshold(model) ||\n\t\t\t\ts.sawtoothTrigger.GetLastTokens(threadID) > s.effectiveTokenThreshold(model) {\n\t\t\t\t// Fresh tail grew too large — invalidate and re-stub\n\t\t\t\ts.logger.Printf(\"[req %d %s tid=%s] SAWTOOTH: frozen prefix expired (%dk frozen + %dk fresh + %dk overhead = %dk > %dk threshold)\",\n\t\t\t\t\treqIdx, proj, threadID, frozen.Tokens/1000, freshTokens/1000, overhead/1000, combinedTokens/1000, s.effectiveTokenThreshold(model)/1000)\n\t\t\t\ts.frozenStubs.Invalidate(threadID)\n\t\t\t\tfrozen = nil // fall through to trigger check below\n\t\t\t} else {\n\t\t\t\t// Use frozen prefix + fresh tail\n\t\t\t\tcombined := make([]any, 0, len(frozen.Messages)+len(freshMessages))\n\t\t\t\tcombined = append(combined, frozen.Messages...)\n\t\t\t\tcombined = append(combined, freshMessages...)\n\t\t\t\treq[\"messages\"] = combined\n\t\t\t\t// Eager-stub large tool_results in the fresh tail (uncached → zero cache cost)\n\t\t\t\tbeforeEager := s.countMessageTokens(combined)\n\t\t\t\tcombined = EagerStubToolResults(combined, len(frozen.Messages), s.countTokens)\n\t\t\t\tafterEager := s.countMessageTokens(combined)\n\t\t\t\tif beforeEager != afterEager {\n\t\t\t\t\ts.logger.Printf(\"[req %d %s tid=%s] EAGER-STUB: %dk → %dk tokens (saved %dk)\",\n\t\t\t\t\t\treqIdx, proj, threadID, beforeEager/1000, afterEager/1000, (beforeEager-afterEager)/1000)\n\t\t\t\t}\n\t\t\t\treq[\"messages\"] = combined\n\t\t\t\tneedsReserialization = true\n\t\t\t\ts.logger.Printf(\"%s[req %d %s tid=%s] SAWTOOTH: frozen prefix (%d stubs, %dk msg) + %d fresh (%dk msg) + %dk overhead = %dk total%s\",\n\t\t\t\t\tcolorGreen, reqIdx, proj, threadID, len(frozen.Messages), frozen.Tokens/1000, len(freshMessages), freshTokens/1000, overhead/1000, combinedTokens/1000, colorReset)\n\t\t\t\trawMsgTokens := s.countMessageTokens(messages)\n\t\t\t\ts.setRawEstimate(threadID, rawMsgTokens+overhead)\n\t\t\t\ts.logger.Printf(\"[req %d %s tid=%s] RAW ESTIMATE: msgTokens=%dk + overhead=%dk = %dk (messages=%d)\", reqIdx, proj, threadID, rawMsgTokens/1000, overhead/1000, (rawMsgTokens+overhead)/1000, len(messages))\n\t\t\t\tif InjectFrozenStubCacheBreakpoint(req, len(frozen.Messages)) {\n\t\t\t\t\ts.logger.Printf(\"[req %d %s tid=%s] SAWTOOTH: frozen stub cache breakpoint at messages[%d]\",\n\t\t\t\t\t\treqIdx, proj, threadID, len(frozen.Messages)-1)\n\t\t\t\t}\n\n\t\t\t\t// Ensure frozen stubs are persisted (may have failed on first request after deploy)\n\t\t\t\ts.ensureFrozenPersisted(threadID)\n\t\t\t}\n\t\t}\n\t\tif frozen == nil {\n\t\t\t// No frozen stubs — check trigger\n\t\t\ttriggerReason := s.sawtoothTrigger.ShouldTrigger(threadID, totalTokens)\n\t\t\tif triggerReason != TriggerNone {\n\t\t\t\ts.logger.Printf(\"[req %d %s tid=%s] SAWTOOTH stub-cycle triggered: %s (tokens=%dk)\",\n\t\t\t\t\treqIdx, proj, threadID, triggerReason, totalTokens/1000)\n\n\t\t\t\t// Refresh briefing during stub-cycle — disabled (briefing now via SessionStart hook)\n\t\t\t\t// s.refreshBriefing(proj)\n\t\t\t\t// if s.briefingText != \"\" {\n\t\t\t\t// \tUpsertSystemBlockCached(req, \"yesmem-briefing\", s.briefingText)\n\t\t\t\t// }\n\n\t\t\t\t// Run full stub pipeline\n\t\t\t\ts.setRawEstimate(threadID, totalTokens)\n\t\t\t\t_ = s.runStubCycle(messages, req, reqIdx, proj, threadID, overhead, totalTokens, userQuery, isRetryReq)\n\n\t\t\t\t// Freeze the result\n\t\t\t\tfinalMessages, _ := req[\"messages\"].([]any)\n\t\t\t\tif finalMessages != nil && len(finalMessages) > 0 {\n\t\t\t\t\t// Boundary = last message of original that maps to last stub\n\t\t\t\t\tcutoff := len(messages)\n\t\t\t\t\tvar boundaryMsg any\n\t\t\t\t\tif cutoff > 0 {\n\t\t\t\t\t\tboundaryMsg = messages[cutoff-1]\n\t\t\t\t\t}\n\t\t\t\t\tfrozenTokens := s.countMessageTokens(finalMessages)\n\t\t\t\t\ts.frozenStubs.Store(threadID, finalMessages, cutoff, boundaryMsg, frozenTokens, totalTokens)\n\t\t\t\t\ts.decay.Persist(threadID)\n\t\t\t\t\ts.logger.Printf(\"[req %d %s tid=%s] SAWTOOTH: frozen %d messages at cutoff=%d (~%dk tokens)\",\n\t\t\t\t\t\treqIdx, proj, threadID, len(finalMessages), cutoff, frozenTokens/1000)\n\t\t\t\t\tif InjectFrozenStubCacheBreakpoint(req, len(finalMessages)) {\n\t\t\t\t\t\ts.logger.Printf(\"[req %d %s tid=%s] SAWTOOTH: frozen stub cache breakpoint at messages[%d]\",\n\t\t\t\t\t\t\treqIdx, proj, threadID, len(finalMessages)-1)\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tneedsReserialization = true\n\t\t\t} else {\n\t\t\t\t// No trigger, no frozen stubs — pass through (early conversation)\n\t\t\t\t// Eager-stub large tool_results even before first stub cycle\n\t\t\t\tbeforeEager := s.countMessageTokens(messages)\n\t\t\t\tmessages = EagerStubToolResults(messages, 0, s.countTokens)\n\t\t\t\tafterEager := s.countMessageTokens(messages)\n\t\t\t\tif beforeEager != afterEager {\n\t\t\t\t\ts.logger.Printf(\"[req %d %s tid=%s] EAGER-STUB: %dk → %dk tokens (saved %dk)\",\n\t\t\t\t\t\treqIdx, proj, threadID, beforeEager/1000, afterEager/1000, (beforeEager-afterEager)/1000)\n\t\t\t\t}\n\t\t\t\treq[\"messages\"] = messages\n\t\t\t\ts.logger.Printf(\"%s[req %d %s tid=%s] %d messages, ~%dk tokens (sawtooth: no trigger)%s\",\n\t\t\t\t\tcolorDim, reqIdx, proj, threadID, len(messages), totalTokens/1000, colorReset)\n\t\t\t}\n\t\t}\n\t} else {\n\t\t// Legacy path: progressive decay\n\t\tif s.shouldStub(totalTokens, model) {\n\t\t\ts.setRawEstimate(threadID, totalTokens)\n\t\t\t_ = s.runStubCycle(messages, req, reqIdx, proj, threadID, overhead, totalTokens, userQuery, isRetryReq)\n\t\t\tneedsReserialization = true\n\t\t}\n\t}\n\n\t// Associative context injection (for ALL requests, not just stubbed ones)\n\t// Re-enabled with higher thresholds (55/75, max 1) after disabling Fresh Remember.\n\tassocContext := s.findAssociativeContextFor(userQuery, proj, threadID, messages)\n\tif assocContext != \"\" {\n\t\tcurrentMessages, _ := req[\"messages\"].([]any)\n\t\tif currentMessages == nil {\n\t\t\tcurrentMessages = messages\n\t\t}\n\t\treq[\"messages\"] = injectAssociativeContext(currentMessages, assocContext, s.cfg.SawtoothEnabled)\n\t\tneedsReserialization = true\n\t\ts.logger.Printf(\"%s[req %d %s tid=%s] associative context injected%s\", colorBlue, reqIdx, proj, threadID, colorReset)\n\t}\n\n\t// Doc-chunk context injection: separate search path for indexed docs (not skills)\n\tdocContext := s.findDocContextFor(userQuery, proj, messages)\n\tif docContext != \"\" {\n\t\tcurrentMessages, _ := req[\"messages\"].([]any)\n\t\tif currentMessages == nil {\n\t\t\tcurrentMessages = messages\n\t\t}\n\t\treq[\"messages\"] = injectAssociativeContext(currentMessages, docContext, s.cfg.SawtoothEnabled)\n\t\tneedsReserialization = true\n\t\ts.logger.Printf(\"%s[req %d %s tid=%s] doc context injected%s\", colorBlue, reqIdx, proj, threadID, colorReset)\n\t}\n\n\t// Fresh Remember Injection: DISABLED\n\t// Causes echo-loop: Claude saves via remember(), sees it again next turn as\n\t// [yesmem fresh memory], reacts to own output instead of user's request.\n\t// Recovery after /clear is handled by recovery.go (briefing), not here.\n\t// Subagents get their own briefing. No remaining use-case.\n\t// remItems := s.popRecentRemember()\n\tvar remItems []recentLearningItem\n\n\t// Rules re-injection: inject condensed CLAUDE.md rules every ~40k output tokens\n\tif rulesBlock := s.rulesInject(threadID, totalTokens, proj); rulesBlock != \"\" {\n\t\tcurrentMessages, _ := req[\"messages\"].([]any)\n\t\tif currentMessages == nil {\n\t\t\tcurrentMessages = messages\n\t\t}\n\t\treq[\"messages\"] = injectAssociativeContext(currentMessages, s.formatRulesReminder(rulesBlock, proj), s.cfg.SawtoothEnabled)\n\t\tneedsReserialization = true\n\t\ts.logger.Printf(\"%s[req %d %s tid=%s] rules reminder injected%s\", colorBlue, reqIdx, proj, threadID, colorReset)\n\t}\n\n\t// Plan auto-activation: nudge Claude to call set_plan() when reading a plan file\n\tif planFile := detectPlanFileRead(messages); planFile != \"\" {\n\t\tif activePlan, _ := s.getActivePlan(threadID); shouldNudgePlan(planFile, activePlan) {\n\t\t\tAppendSystemBlock(req, \"plan-nudge\",\n\t\t\t\tfmt.Sprintf(\"Du hast einen Implementierungsplan gelesen (%s). Aktiviere ihn jetzt via set_plan() damit Plan-Checkpoints funktionieren und dein Fortschritt getrackt wird.\", filepath.Base(planFile)))\n\t\t\tneedsReserialization = true\n\t\t\ts.logger.Printf(\"%s[req %d %s tid=%s] plan nudge injected for %s%s\", colorBlue, reqIdx, proj, threadID, filepath.Base(planFile), colorReset)\n\t\t}\n\t}\n\n\t// Plan checkpoint: inject plan reminder every ~20k tokens if active plan exists\n\ts.detectPlanToolCall(messages, threadID, totalTokens)\n\tif checkpoint := s.planCheckpointInject(threadID, totalTokens); checkpoint != \"\" {\n\t\tcurrentMessages, _ := req[\"messages\"].([]any)\n\t\tif currentMessages == nil {\n\t\t\tcurrentMessages = messages\n\t\t}\n\t\treq[\"messages\"] = injectAssociativeContext(currentMessages, checkpoint, s.cfg.SawtoothEnabled)\n\t\tneedsReserialization = true\n\t\ts.logger.Printf(\"%s[req %d %s tid=%s] plan checkpoint injected%s\", colorBlue, reqIdx, proj, threadID, colorReset)\n\t}\n\n\t// Docs hint injection: inject docs-available reminder every ~10k tokens (independent of plan)\n\tif docsHint := s.docsHintInject(threadID, totalTokens, proj); docsHint != \"\" {\n\t\tcurrentMessages, _ := req[\"messages\"].([]any)\n\t\tif currentMessages == nil {\n\t\t\tcurrentMessages = messages\n\t\t}\n\t\treq[\"messages\"] = injectAssociativeContext(currentMessages, docsHint, s.cfg.SawtoothEnabled)\n\t\tneedsReserialization = true\n\t\ts.logger.Printf(\"%s[req %d %s tid=%s] docs hint injected%s\", colorBlue, reqIdx, proj, threadID, colorReset)\n\t}\n\tif len(remItems) > 0 {\n\t\tvar lines []string\n\t\tfor _, item := range remItems {\n\t\t\tif item.ID > 0 {\n\t\t\t\tlines = append(lines, fmt.Sprintf(\"- [ID:%d] %s\", item.ID, truncateStr(item.Text, 200)))\n\t\t\t} else {\n\t\t\t\tlines = append(lines, \"- \"+truncateStr(item.Text, 200))\n\t\t\t}\n\t\t}\n\t\tremContext := fmt.Sprintf(\"[yesmem fresh memory]\\nGerade gelernt:\\n%s\\n%s\\n[/yesmem fresh memory]\", strings.Join(lines, \"\\n\"), InlineReflectionHint)\n\t\tcurrentMessages, _ := req[\"messages\"].([]any)\n\t\tif currentMessages == nil {\n\t\t\tcurrentMessages = messages\n\t\t}\n\t\treq[\"messages\"] = injectAssociativeContext(currentMessages, remContext, s.cfg.SawtoothEnabled)\n\t\tneedsReserialization = true\n\t\ts.logger.Printf(\"%s[req %d %s tid=%s] fresh remember injected (%d items)%s\", colorBlue, reqIdx, proj, threadID, len(remItems), colorReset)\n\t}\n\n\t// Skill eval injection: mandatory evaluation instruction for real user input only\n\tif threadID != \"\" && proj != \"\" && isUserInputTurn(messages) {\n\t\t// First: detect any skill activations from message history (updates tracker)\n\t\ts.syncSkillActivations(messages, proj, threadID)\n\n\t\t// Inject mandatory skill evaluation block into last user message\n\t\tskillEval := buildSkillEvalBlock()\n\t\tcurrentMessages, _ := req[\"messages\"].([]any)\n\t\tif currentMessages == nil {\n\t\t\tcurrentMessages = messages\n\t\t}\n\t\treq[\"messages\"] = injectAssociativeContext(currentMessages, skillEval, s.cfg.SawtoothEnabled)\n\t\tneedsReserialization = true\n\t\ts.logger.Printf(\"%s[req %d %s tid=%s] skill-eval injected%s\", colorBlue, reqIdx, proj, threadID, colorReset)\n\t}\n\n\t// === Collect injected learning IDs for next turn's inline reflection ===\n\tif threadID != \"\" {\n\t\tcurrentIDs := make(map[int64]string) // id → source\n\t\t// Source 1: Briefing learnings (cached, same every turn)\n\t\ts.briefingMu.RLock()\n\t\tbriefingSnapshot := s.briefingText\n\t\ts.briefingMu.RUnlock()\n\t\tif briefingSnapshot != \"\" {\n\t\t\tfor _, m := range idPattern.FindAllStringSubmatch(briefingSnapshot, -1) {\n\t\t\t\tif id, err := strconv.ParseInt(m[1], 10, 64); err == nil {\n\t\t\t\t\tcurrentIDs[id] = \"briefing\"\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\t// Source 2: Associative context (contextual, per-turn)\n\t\tif assocContext != \"\" {\n\t\t\tfor _, m := range idPattern.FindAllStringSubmatch(assocContext, -1) {\n\t\t\t\tif id, err := strconv.ParseInt(m[1], 10, 64); err == nil {\n\t\t\t\t\tcurrentIDs[id] = \"associative\" // overwrites briefing if both — associative is the stronger signal\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\t// Source 3: Fresh remember items (just learned)\n\t\tfor _, item := range remItems {\n\t\t\tif item.ID > 0 {\n\t\t\t\tcurrentIDs[item.ID] = \"fresh\"\n\t\t\t}\n\t\t}\n\t\ts.lastInjectedIDsMu.Lock()\n\t\tif len(currentIDs) > 0 {\n\t\t\t// Accumulate IDs across turns — don't overwrite.\n\t\t\t// Claude may reference an ID from turn 30 in turn 50.\n\t\t\texisting := s.lastInjectedIDs[threadID]\n\t\t\tif existing == nil {\n\t\t\t\texisting = make(map[int64]string)\n\t\t\t}\n\t\t\tfor id, source := range currentIDs {\n\t\t\t\texisting[id] = source\n\t\t\t}\n\t\t\ts.lastInjectedIDs[threadID] = existing\n\t\t}\n\t\ts.lastInjectedIDsMu.Unlock()\n\t}\n\n\t// Think reminder injection — only for real user turns, not tool continuations\n\tif threadID != \"\" {\n\t\tcurrentMessages, _ := req[\"messages\"].([]any)\n\t\tif currentMessages == nil {\n\t\t\tcurrentMessages = messages\n\t\t}\n\t\tif lastUserHasText(currentMessages) {\n\t\t\tthinkReminder := s.buildThinkReminder(threadID, threadID, false)\n\t\t\tif thinkReminder != \"\" {\n\t\t\t\treq[\"messages\"] = injectAssociativeContext(currentMessages, thinkReminder, s.cfg.SawtoothEnabled)\n\t\t\t\tneedsReserialization = true\n\t\t\t}\n\t\t}\n\n\t\t// Dialog injection: append at end of messages with pseudo-padding for alternation.\n\t\t// Always works regardless of tool_result chains or conversation state.\n\t\tdr := s.checkDialogMessages(threadID, proj)\n\t\tif dr.Extra != \"\" {\n\t\t\t// Prepend session ID for agent-to-agent communication\n\t\t\tdr.Extra = fmt.Sprintf(\"DEINE_SESSION_ID: %s\\n\", threadID) + dr.Extra\n\t\t\tinjected := false\n\t\t\tif msgs, ok := req[\"messages\"].([]any); ok && len(msgs) > 0 {\n\t\t\t\t// Pad with empty assistant if last message is user (maintains alternation)\n\t\t\t\tlastMsg, _ := msgs[len(msgs)-1].(map[string]any)\n\t\t\t\tif lastMsg != nil && lastMsg[\"role\"] == \"user\" {\n\t\t\t\t\tmsgs = append(msgs, map[string]any{\n\t\t\t\t\t\t\"role\": \"assistant\",\n\t\t\t\t\t\t\"content\": \"​\", // zero-width space — invisible padding\n\t\t\t\t\t})\n\t\t\t\t}\n\t\t\t\t// Append dialog as new user message (becomes the last message Claude sees)\n\t\t\t\tmsgs = append(msgs, map[string]any{\n\t\t\t\t\t\"role\": \"user\",\n\t\t\t\t\t\"content\": \"\\n\" + dr.Extra + \"\\n\",\n\t\t\t\t})\n\t\t\t\treq[\"messages\"] = msgs\n\t\t\t\tneedsReserialization = true\n\t\t\t\tinjected = true\n\t\t\t}\n\t\t\t// Mark as read after 3 injection turns — long enough for Claude to process,\n\t\t\t// resilient against fast ping-pong sessions (time-based was broken for <30s turns).\n\t\t\tif injected && dr.HasUnread && dr.SessionID != \"\" {\n\t\t\t\tif s.shouldMarkChannelRead(dr) {\n\t\t\t\t\ts.markDialogRead(dr)\n\t\t\t\t}\n\t\t\t}\n\t\t\ts.logger.Printf(\"[dialog] injection injected=%v extra_len=%d\", injected, len(dr.Extra))\n\t\t}\n\t}\n\n\t// Signal tools are NOT injected into the main request.\n\t// They are collected via a separate async reflection call after the response.\n\t// See fireReflectionCall() in reflection.go.\n\t// NEVER put _signal_* tools back here — the model ignores them among 44+ real tools.\n\n\t// Metadata injection: timestamp, message index, time-delta for messages after cache breakpoint.\n\t// Messages before the breakpoint are cached — modifying them would break the cache.\n\tif threadID != \"\" {\n\t\tif msgs, ok := req[\"messages\"].([]any); ok && len(msgs) > 0 {\n\t\t\tnow := time.Now()\n\t\t\tnowStr := shortWeekday(now.Weekday()) + \" \" + now.Format(\"2006-01-02 15:04:05\")\n\n\t\t\t// Find last cache_control breakpoint in messages\n\t\t\tcacheIdx := -1\n\t\t\tfor i, m := range msgs {\n\t\t\t\tmsg, _ := m.(map[string]any)\n\t\t\t\tif msg == nil {\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\t\t\t\tif _, has := msg[\"cache_control\"]; has {\n\t\t\t\t\tcacheIdx = i\n\t\t\t\t}\n\t\t\t\tif content, ok := msg[\"content\"].([]any); ok {\n\t\t\t\t\tfor _, block := range content {\n\t\t\t\t\t\tif b, ok := block.(map[string]any); ok {\n\t\t\t\t\t\t\tif _, has := b[\"cache_control\"]; has {\n\t\t\t\t\t\t\t\tcacheIdx = i\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tstartIdx := cacheIdx + 1\n\t\t\tif startIdx < 0 {\n\t\t\t\tstartIdx = 0\n\t\t\t}\n\n\t\t\t// Retrieve stored response time for assistant annotation\n\t\t\ts.responseTsMu.RLock()\n\t\t\trespTime, hasRespTime := s.responseTimes[threadID]\n\t\t\ts.responseTsMu.RUnlock()\n\n\t\t\t// 1. Last user message: annotate with [2026-03-13 00:15:42] [msg:N]\n\t\t\tlastMsg, _ := msgs[len(msgs)-1].(map[string]any)\n\t\t\tif lastMsg != nil && lastMsg[\"role\"] == \"user\" {\n\t\t\t\tmeta := fmt.Sprintf(\"[%s] [msg:%d]\", nowStr, s.msgCounters.nextFor(threadID, len(msgs)-1))\n\t\t\t\tif hasRespTime {\n\t\t\t\t\tdelta := now.Sub(respTime)\n\t\t\t\t\tmeta += \" [+\" + formatDelta(delta) + \"]\"\n\t\t\t\t}\n\t\t\t\tprependMeta(lastMsg, meta)\n\t\t\t\tneedsReserialization = true\n\t\t\t}\n\n\t\t\t// 2. Last assistant message after cache breakpoint: use stored response time\n\t\t\tif hasRespTime {\n\t\t\t\tfor i := len(msgs) - 1; i >= startIdx; i-- {\n\t\t\t\t\tmsg, _ := msgs[i].(map[string]any)\n\t\t\t\t\tif msg != nil && msg[\"role\"] == \"assistant\" {\n\t\t\t\t\t\tmeta := fmt.Sprintf(\"[%s] [msg:%d]\", respTime.Format(\"2006-01-02 15:04:05\"), i)\n\t\t\t\t\t\tprependMeta(msg, meta)\n\t\t\t\t\t\tneedsReserialization = true\n\t\t\t\t\t\tbreak\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\t// Self-Priming: DISABLED (experimental, see signal_handlers.go comment block).\n\t// The reflection-based selfprime (Haiku) was too inaccurate to be useful —\n\t// the main model adapts to user context organically from message history.\n\t// Kept for reference; re-enable by uncommenting below + setting ShouldActivate=true.\n\t//\n\t// s.selfPrimeMu.RLock()\n\t// anchor := s.selfPrimes[threadID]\n\t// s.selfPrimeMu.RUnlock()\n\t// if anchor != \"\" {\n\t// \tif msgs, ok := req[\"messages\"].([]any); ok && len(msgs) > 0 {\n\t// \t\tlastMsg, _ := msgs[len(msgs)-1].(map[string]any)\n\t// \t\tif lastMsg != nil && lastMsg[\"role\"] == \"user\" {\n\t// \t\t\tprimeBlock := map[string]any{\n\t// \t\t\t\t\"type\": \"text\",\n\t// \t\t\t\t\"text\": \"[self-prime]\\n[Self-Prime von deiner letzten Antwort]: \" + anchor,\n\t// \t\t\t}\n\t// \t\t\tif content, ok := lastMsg[\"content\"].([]any); ok {\n\t// \t\t\t\tlastMsg[\"content\"] = append(content, primeBlock)\n\t// \t\t\t} else if text, ok := lastMsg[\"content\"].(string); ok {\n\t// \t\t\t\tlastMsg[\"content\"] = []any{\n\t// \t\t\t\t\tmap[string]any{\"type\": \"text\", \"text\": text},\n\t// \t\t\t\t\tprimeBlock,\n\t// \t\t\t\t}\n\t// \t\t\t}\n\t// \t\t\tneedsReserialization = true\n\t// \t\t}\n\t// \t}\n\t// }\n\n\t// Prompt Caching: inject cache_control breakpoints on system + tools\n\t// Sawtooth: disabled (all 4 slots are used by CC system (2) + briefing (1) + CC messages (1))\n\t// Legacy: count existing breakpoints and fill up remaining slots.\n\tif !s.cfg.SawtoothEnabled && s.cacheGate.ShouldCache() {\n\t\tif n := InjectCacheBreakpoints(req, s.logger); n > 0 {\n\t\t\tneedsReserialization = true\n\t\t\ts.logger.Printf(\"[req %d %s] prompt cache: %d breakpoints injected\", reqIdx, proj, n)\n\t\t} else {\n\t\t\ts.logger.Printf(\"[req %d %s] prompt cache: no budget (4/4 already set)\", reqIdx, proj)\n\t\t}\n\t}\n\n\t// Final: upgrade cache TTL + enforce breakpoint limit (must run AFTER all injections + InjectCacheBreakpoints)\n\t// TTL upgrade only for API key auth — subscription (OAuth) users should not get extended TTL.\n\tif s.cfg.CacheTTL != \"\" && s.cfg.CacheTTL != \"ephemeral\" && IsAPIKeyAuth(r.Header) {\n\t\tif n := UpgradeCacheTTL(req, s.cfg.CacheTTL); n > 0 {\n\t\t\tneedsReserialization = true\n\t\t\ts.logger.Printf(\"[req %d %s] cache TTL upgraded: %d blocks → %s\", reqIdx, proj, n, s.cfg.CacheTTL)\n\t\t}\n\t}\n\tif n := EnforceCacheBreakpointLimit(req, maxCacheBreakpoints); n > 0 {\n\t\tneedsReserialization = true\n\t\ts.logger.Printf(\"[req %d %s] prompt cache: trimmed %d surplus breakpoints\", reqIdx, proj, n)\n\t}\n\n\t// Pre-flight: validate tool_use/tool_result pairing before forwarding.\n\t// Repairs orphans created by Stubify/Collapse/Injection pipeline.\n\tif msgs, ok := req[\"messages\"].([]any); ok {\n\t\trepaired, orphans := validateToolPairs(msgs, s.logger)\n\t\tif orphans > 0 {\n\t\t\treq[\"messages\"] = repaired\n\t\t\ts.logger.Printf(\"%s[req %d %s tid=%s] validate: repaired %d orphan tool_result(s)%s\", colorOrange, reqIdx, proj, threadID, orphans, colorReset)\n\t\t\tneedsReserialization = true\n\t\t}\n\t}\n\n\t// Early-out: skip re-serialization when nothing changed\n\tif !needsReserialization {\n\t\ts.logger.Printf(\"%s[req %d %s tid=%s] %d messages, ~%dk tokens (no stubbing, threshold=%dk)%s\", colorDim, reqIdx, proj, threadID, len(messages), totalTokens/1000, s.effectiveTokenThreshold(model)/1000, colorReset)\n\t\ts.forwardWithAnnotation(w, r, body, reqIdx, toolUseIDs, proj, threadID, msgCount, totalTokens)\n\t\treturn\n\t}\n\n\t// Re-serialize\n\tbody, err = json.Marshal(req)\n\tif err != nil {\n\t\ts.logger.Printf(\"%s[req %d %s tid=%s] re-marshal error: %v%s\", colorRed, reqIdx, proj, threadID, err, colorReset)\n\t\thttp.Error(w, \"internal proxy error\", http.StatusInternalServerError)\n\t\treturn\n\t}\n\n\t// Debug: dump final request body to file (enable with YESMEM_PROXY_DEBUG=1)\n\tif needsReserialization && os.Getenv(\"YESMEM_PROXY_DEBUG\") == \"1\" {\n\t\tdumpDir := filepath.Join(s.cfg.DataDir, \"debug\")\n\t\tos.MkdirAll(dumpDir, 0755)\n\t\tts := time.Now().Format(\"20060102-150405\")\n\t\tdumpPath := fmt.Sprintf(\"%s/req_%s_%d.json\", dumpDir, ts, reqIdx)\n\t\tos.WriteFile(dumpPath, body, 0644)\n\t\ts.logger.Printf(\"[req %d] dumped %dk to %s\", reqIdx, len(body)/1024, dumpPath)\n\t}\n\n\t// Task #7: Mark this request fingerprint for retry detection\n\tif !isRetryReq {\n\t\ts.markRequest(fp)\n\t}\n\n\t// Forward to Anthropic with SSE annotation extraction\n\t// Use original msgCount (pre-injection) so actual-based estimation stays consistent across turns\n\t// Use actual-based totalTokens as estimate when available (more accurate than full recount)\n\tfinalEstimate := totalTokens\n\tif s.sawtoothTrigger == nil || s.sawtoothTrigger.GetLastTokens(threadID) == 0 {\n\t\t// First request: no actual yet, do a full recount of post-injection messages\n\t\tfinalMsgs, _ := req[\"messages\"].([]any)\n\t\tfinalEstimate = s.measureOverhead(req) + s.countMessageTokens(finalMsgs)\n\t}\n\ts.logger.Printf(\"[req %d %s tid=%s] FINAL: ~%dk estimated (after all injections)\", reqIdx, proj, threadID, finalEstimate/1000)\n\tif raw := s.getRawEstimate(threadID); raw > 0 {\n\t\ts.cacheStatusWriter.UpdateRawForThread(threadID, raw)\n\t}\n\ts.forwardWithAnnotation(w, r, body, reqIdx, toolUseIDs, proj, threadID, msgCount, finalEstimate)\n}\n\n// bytesPerToken is the approximation factor for token estimation.\n// Anthropic recommends ~3.5 chars/token; we use 3.6 for slight overestimation\n// (triggers stubbing a bit earlier = safer for context budget).\nconst bytesPerToken = 3.6\n\n// TokenEstimateFunc is the central token estimation function.\n// All stubbing/budget decisions use this. Receives extracted text content\n// (not JSON wire format) so the tokenizer counts actual content tokens.\ntype TokenEstimateFunc func(text string) int\n"}, "3c031b680946a05d1204a05c531f214ae3cc4b76": {"t": 1785759373.0190775, "n": 52787, "h": "b1d2184288b0cf7f", "c": "package proxy\n\nimport (\n\t\"context\"\n\t\"crypto/tls\"\n\t\"encoding/json\"\n\t\"fmt\"\n\t\"io\"\n\t\"log\"\n\t\"net\"\n\t\"net/http\"\n\t\"os\"\n\t\"os/signal\"\n\t\"path/filepath\"\n\t\"strconv\"\n\t\"strings\"\n\t\"sync\"\n\t\"sync/atomic\"\n\t\"syscall\"\n\t\"time\"\n\n\ttokenizer \"github.com/qhenkart/anthropic-tokenizer-go\"\n)\n\n// Config holds proxy configuration.\ntype Config struct {\n\tListenAddr string // e.g. \":9099\"\n\tTargetURL string // e.g. \"https://api.anthropic.com\"\n\tTokenThreshold int // trigger stubbing above this estimated token count\n\tTokenMinimumThreshold int // stub down to this floor\n\tTokenThresholds map[string]int // model-specific thresholds: {\"opus\": 180000, \"haiku\": 130000}\n\tKeepRecent int // number of messages to always keep unmodified\n\tDataDir string // yesmem data directory for DB access\n\tOpenAITargetURL string // upstream for OpenAI-format clients; if empty, uses TargetURL\n\n\t// Signal reflection\n\tSignalsEnabled bool // enable async signal reflection calls\n\tSignalsMode string // \"reflection\" = separate API call\n\tSignalsEveryNTurns int // every N end_turn responses\n\tSignalsModel string // full model ID, e.g. \"claude-sonnet-4-6\"\n\tAPIKey string // needed for reflection API calls\n\n\t// Sawtooth cache optimization\n\tSawtoothEnabled bool // use sawtooth instead of progressive decay (default: true)\n\tCacheTTL string // \"ephemeral\" (5m) or \"1h\" (extended)\n\n\t// Usage deflation: scale down input_tokens reported to CC to suppress \"Context low\" warning.\n\t// 0 = disabled, 0.7 = report 70% of actual tokens. Real values kept for internal tracking.\n\tUsageDeflationFactor float64\n\n\t// System prompt rewriting\n\tPromptUngate bool // strip \"may or may not be relevant\" disclaimer from CLAUDE.md injection\n\tPromptRewrite bool // strip \"Output efficiency\" + \"short and concise\", inject Ant-quality directives\n\tPromptEnhance bool // CLAUDE.md authority reinforcement, comment discipline, persona-based tone\n\tEffortFloor string // minimum effort level: \"low\", \"medium\", \"high\", \"max\" (empty = off)\n\n\t// Cache keepalive\n\tCacheKeepaliveEnabled bool\n\tCacheKeepaliveMode string // \"auto\", \"5m\", \"1h\"\n\tCacheKeepalivePings5m int\n\tCacheKeepalivePings1h int\n\n\t// Forked agents\n\tForkedAgentsEnabled bool\n\tForkedAgentsModel string // full model ID\n\tForkedAgentsTokenGrowthTrigger int\n\tForkedAgentsMaxFailures int\n\tForkedAgentsMaxForksPerSession int\n\tForkedAgentsDebug bool\n\n\t// Cache state management\n\tResetCache bool // clear persisted frozen stubs and decay state on startup\n}\n\nconst maxAnnotations = 5000 // evict oldest when exceeded\n\n// ANSI color codes for log output\nconst (\n\tcolorReset = \"\\033[0m\"\n\tcolorDim = \"\\033[2m\" // gray — passthrough / no stubbing\n\tcolorGreen = \"\\033[32m\" // stubbed / compressed\n\tcolorLightGreen = \"\\033[92m\" // FINAL without compression\n\tcolorBlue = \"\\033[34m\" // injection\n\tcolorOrange = \"\\033[33m\" // skip / retry\n\tcolorRed = \"\\033[31m\" // errors\n)\n\n// Server is the infinite-thread proxy.\ntype Server struct {\n\tcfg Config\n\thttpClient *http.Client\n\tlogger *log.Logger\n\trequestIdx atomic.Int64\n\n\t// Post-hoc annotations: tool_use_id → first ~120 chars of Claude's response\n\tmu sync.RWMutex\n\tannotations map[string]string\n\n\t// Progressive decay tracker\n\tdecay *DecayTracker\n\n\t// Living narrative block\n\tnarrative *Narrative\n\n\t// Pivot moments cache (from daemon)\n\tpivotMu sync.RWMutex\n\tpivotTexts []string\n\tpivotCached time.Time\n\n\t// Aggregate stats (Task #2)\n\tstats *ProxyStats\n\n\t// Local tokenizer for accurate token counting\n\ttokenizer *tokenizer.Tokenizer\n\n\t// Cached overhead tokens (system + tools), set on first request per thread\n\toverheadMu sync.RWMutex\n\toverheadTokens int // unused, kept for interface compat\n\n\t// Hysteresis state for stubbing (Task #6)\n\tstubActive atomic.Bool\n\n\t// Daemon connection state — first successful connect flips to true\n\tdaemonReady atomic.Bool\n\n\t// Retry detection (Task #7)\n\tretryMu sync.RWMutex\n\tlastFingerprint string\n\n\t// Session start time for temporal annotation in archive blocks\n\tsessionStartMu sync.RWMutex\n\tsessionStartTime time.Time\n\n\t// Runtime config override (set via MCP set_config tool)\n\tconfigOverrideMu sync.RWMutex\n\ttokenThresholdOverrides map[string]int // model-key → threshold, \"\" = global fallback\n\n\t// Briefing cache — loaded once per thread, injected as user/assistant message pair\n\tbriefingMu sync.RWMutex\n\tbriefingText string\n\n\t// Cognitive signal bus — routes _signal_* tool calls to handlers\n\tsignalBus *SignalBus\n\n\t// Inline reflection: track which learning IDs were injected per thread\n\tlastInjectedIDsMu sync.Mutex\n\tlastInjectedIDs map[string]map[int64]string // threadID → id → source (\"briefing\"|\"associative\"|\"fresh\")\n\tsessionInjectCounts map[string]map[int64]int // threadID → learningID → injection count this session\n\tlastTurnInjected map[string]map[int64]bool // threadID → learningIDs injected in previous turn\n\n\t// Self-priming anchors: threadID → last self-prime text\n\tselfPrimeMu sync.RWMutex\n\tselfPrimes map[string]string\n\n\t// Timestamp tracking: threadID → time of last response completion\n\tresponseTsMu sync.RWMutex\n\tresponseTimes map[string]time.Time\n\n\t// Think reminder: per-thread request counter (replaces hook-think file-based counter)\n\tthinkMu sync.Mutex\n\tthinkCounters map[string]int // threadID → request count\n\n\tchannelMu sync.Mutex\n\tchannelInjectCount map[string]int // sessionID → injection turn count\n\n\t// Prompt cache gating — enables cache_control breakpoints when requests are frequent\n\tcacheGate *CacheGate\n\n\t// Sawtooth cache optimization\n\tfrozenStubs *FrozenStubs\n\tsawtoothTrigger *SawtoothTrigger\n\tcacheStatusWriter *CacheStatusWriter\n\tcacheKeepalive *CacheKeepalive\n\tcacheTTLDetector *CacheTTLDetector\n\n\t// Cumulative token savings from collapsing (per threadID)\n\trawSavingsMu sync.Mutex\n\trawSavings map[string]int\n\n\t// Skill hint tracking: per-thread active skills\n\tskillTracker *skillHintTracker\n\n\t// Forked agent state: per-thread token growth + failure tracking\n\tforkState *ForkState\n\tforkConfigs []ForkConfig\n\n\t// Rules re-injection: condensed CLAUDE.md rules injected every ~40k tokens\n\trulesMu sync.RWMutex\n\trulesBlock string // cached condensed rules (fetched once from daemon)\n\trulesTokenCount map[string]int // threadID → tokens since last rules injection\n\trulesCollapseInjected map[string]bool // threadID → true if collapse already injected rules (reset by normal inject)\n\tmsgCounters *msgCounters // global per-thread msg counter, persists across collapses\n\n\t// Loop detection: per-thread warning state\n\tloopMu sync.Mutex\n\tloopStates map[string]*LoopState // threadID → state\n}\n\n// Run starts the proxy server and blocks until interrupted.\nfunc Run(cfg Config) error {\n\ts := &Server{\n\t\tcfg: cfg,\n\t\thttpClient: &http.Client{\n\t\t\tTimeout: 10 * time.Minute,\n\t\t\tTransport: &http.Transport{\n\t\t\t\tTLSClientConfig: &tls.Config{},\n\t\t\t\tMaxIdleConns: 10,\n\t\t\t\tIdleConnTimeout: 90 * time.Second,\n\t\t\t\tDisableCompression: true,\n\t\t\t\tResponseHeaderTimeout: 60 * time.Second,\n\t\t\t},\n\t\t},\n\t\tlogger: createLogger(cfg.DataDir),\n\t\tannotations: make(map[string]string),\n\t\tdecay: NewDecayTracker(),\n\t\tnarrative: NewNarrative(),\n\t\tstats: &ProxyStats{startTime: time.Now()},\n\t\tselfPrimes: make(map[string]string),\n\t\tlastInjectedIDs: make(map[string]map[int64]string),\n\t\tsessionInjectCounts: make(map[string]map[int64]int),\n\t\tlastTurnInjected: make(map[string]map[int64]bool),\n\t\tresponseTimes: make(map[string]time.Time),\n\t\tthinkCounters: make(map[string]int),\n\t\tchannelInjectCount: make(map[string]int),\n\t\tcacheGate: NewCacheGate(cacheGapForTTL(cfg.CacheTTL)),\n\t\tfrozenStubs: NewFrozenStubsWithTTL(sawtoothTTLForCacheTTL(cfg.CacheTTL)),\n\t\tsawtoothTrigger: NewSawtoothTrigger(cacheGapForTTL(cfg.CacheTTL), cfg.TokenThreshold, cfg.TokenMinimumThreshold),\n\t\tcacheStatusWriter: NewCacheStatusWriter(cfg.DataDir, cfg.CacheTTL, cfg.TokenThreshold, cfg.TokenMinimumThreshold),\n\t\tcacheTTLDetector: NewCacheTTLDetectorWithPersist(cfg.DataDir),\n\t\tskillTracker: newSkillHintTracker(),\n\t\trulesTokenCount: make(map[string]int),\n\t\trulesCollapseInjected: make(map[string]bool),\n\t\tmsgCounters: newMsgCounters(),\n\t\tloopStates: make(map[string]*LoopState),\n\t\tforkState: NewForkState(cfg.ForkedAgentsTokenGrowthTrigger, cfg.ForkedAgentsMaxFailures, cfg.ForkedAgentsMaxForksPerSession),\n\t\tforkConfigs: []ForkConfig{},\n\t}\n\n\t// Log persisted detection state\n\tif sup := s.cacheTTLDetector.Is1hSupported(); sup != nil {\n\t\ts.logger.Printf(\"[proxy] loaded persisted TTL detection: 1h_supported=%v\", *sup)\n\t\tif *sup && s.frozenStubs != nil {\n\t\t\ts.frozenStubs.UpdateTTL(sawtoothTTLForCacheTTL(\"1h\"))\n\t\t}\n\t}\n\n\tif cfg.CacheKeepaliveEnabled {\n\t\ts.cacheKeepalive = NewCacheKeepalive(CacheKeepaliveConfig{\n\t\t\tTarget: cfg.TargetURL,\n\t\t\tMode: cfg.CacheKeepaliveMode,\n\t\t\tPings5m: cfg.CacheKeepalivePings5m,\n\t\t\tPings1h: cfg.CacheKeepalivePings1h,\n\t\t\tDetector: s.cacheTTLDetector,\n\t\t\tLogger: s.logger,\n\t\t\tOnPing: func(threadID string, cacheRead, cacheWrite int) {\n\t\t\t\ts.cacheTTLDetector.RecordResponse(cacheRead, cacheWrite, 0)\n\t\t\t\tif s.frozenStubs != nil {\n\t\t\t\t\ts.frozenStubs.Touch(threadID)\n\t\t\t\t}\n\t\t\t\tif s.sawtoothTrigger != nil {\n\t\t\t\t\ts.sawtoothTrigger.TouchRequestTime(threadID)\n\t\t\t\t}\n\t\t\t},\n\t\t})\n\t}\n\n\t// Wire detector + keepalive to status writer for terminal display\n\ts.cacheStatusWriter.SetDetector(s.cacheTTLDetector)\n\tif s.cacheKeepalive != nil {\n\t\ts.cacheStatusWriter.SetKeepalive(s.cacheKeepalive)\n\t}\n\n\t// Register forked agent configs\n\tif cfg.ForkedAgentsEnabled {\n\t\ts.forkConfigs = append(s.forkConfigs, NewExtractAndEvaluateConfig(cfg.ForkedAgentsModel))\n\t\tmodelDesc := cfg.ForkedAgentsModel\n\t\tif modelDesc == \"\" {\n\t\t\tmodelDesc = \"(same as main thread)\"\n\t\t}\n\t\ts.logger.Printf(\"[proxy] forked agents enabled: %d configs, model=%s, trigger=%dk tokens\",\n\t\t\tlen(s.forkConfigs), modelDesc, cfg.ForkedAgentsTokenGrowthTrigger/1000)\n\t}\n\n\t// Wire sawtooth token persistence via daemon RPC\n\ts.sawtoothTrigger.SetPersistFunc(func(key, value string) {\n\t\tif _, err := s.queryDaemon(\"set_proxy_state\", map[string]any{\"key\": key, \"value\": value}); err != nil {\n\t\t\ts.logger.Printf(\"[sawtooth] persist failed for %s: %v\", key, err)\n\t\t}\n\t})\n\ts.sawtoothTrigger.SetLoadFunc(func(key string) (string, bool) {\n\t\tresult, err := s.queryDaemon(\"get_proxy_state\", map[string]any{\"key\": key})\n\t\tif err != nil || result == nil {\n\t\t\treturn \"\", false\n\t\t}\n\t\tvar resp struct {\n\t\t\tValue string `json:\"value\"`\n\t\t}\n\t\tif json.Unmarshal(result, &resp) != nil || resp.Value == \"\" {\n\t\t\treturn \"\", false\n\t\t}\n\t\treturn resp.Value, true\n\t})\n\n\t// Wire frozen stubs persistence via same daemon RPC\n\ts.frozenStubs.SetPersistFunc(func(key, value string) {\n\t\tif _, err := s.queryDaemon(\"set_proxy_state\", map[string]any{\"key\": key, \"value\": value}); err != nil {\n\t\t\ts.logger.Printf(\"[frozen] persist failed for %s: %v\", key, err)\n\t\t}\n\t})\n\ts.frozenStubs.SetLoadFunc(func(key string) (string, bool) {\n\t\tresult, err := s.queryDaemon(\"get_proxy_state\", map[string]any{\"key\": key})\n\t\tif err != nil || result == nil {\n\t\t\treturn \"\", false\n\t\t}\n\t\tvar resp struct {\n\t\t\tValue string `json:\"value\"`\n\t\t}\n\t\tif json.Unmarshal(result, &resp) != nil || resp.Value == \"\" {\n\t\t\treturn \"\", false\n\t\t}\n\t\treturn resp.Value, true\n\t})\n\n\t// Wire decay tracker persistence via same daemon RPC\n\ts.decay.SetPersistFunc(func(key, value string) {\n\t\tif _, err := s.queryDaemon(\"set_proxy_state\", map[string]any{\"key\": key, \"value\": value}); err != nil {\n\t\t\ts.logger.Printf(\"[decay] persist failed for %s: %v\", key, err)\n\t\t}\n\t})\n\ts.decay.SetLoadFunc(func(key string) (string, bool) {\n\t\tresult, err := s.queryDaemon(\"get_proxy_state\", map[string]any{\"key\": key})\n\t\tif err != nil || result == nil {\n\t\t\treturn \"\", false\n\t\t}\n\t\tvar resp struct {\n\t\t\tValue string `json:\"value\"`\n\t\t}\n\t\tif json.Unmarshal(result, &resp) != nil || resp.Value == \"\" {\n\t\t\treturn \"\", false\n\t\t}\n\t\treturn resp.Value, true\n\t})\n\n\t// Clear persisted cache state if --reset-cache was passed\n\tif cfg.ResetCache {\n\t\tfor _, prefix := range []string{\"frozen:\", \"decay:\"} {\n\t\t\tresult, err := s.queryDaemon(\"delete_proxy_state_prefix\", map[string]any{\"prefix\": prefix})\n\t\t\tif err != nil {\n\t\t\t\ts.logger.Printf(\"[reset-cache] failed to delete %s*: %v\", prefix, err)\n\t\t\t} else {\n\t\t\t\tvar resp struct{ Deleted int }\n\t\t\t\tjson.Unmarshal(result, &resp)\n\t\t\t\ts.logger.Printf(\"[reset-cache] deleted %d %s* entries\", resp.Deleted, prefix)\n\t\t\t}\n\t\t}\n\t}\n\n\t// Init signal bus with daemon RPC as callback\n\ts.signalBus = NewSignalBus(s.logger)\n\tregisterSignalHandlers(s.signalBus, s.logger, s.queryDaemon, func(threadID, anchor string) {\n\t\ts.selfPrimeMu.Lock()\n\t\ts.selfPrimes[threadID] = anchor\n\t\ts.selfPrimeMu.Unlock()\n\t})\n\n\t// Init local tokenizer (BPE-based, ~60ms)\n\ttok, err := tokenizer.New()\n\tif err != nil {\n\t\ts.logger.Printf(\"%s[proxy] tokenizer init failed, falling back to heuristic: %v%s\", colorOrange, err, colorReset)\n\t} else {\n\t\ts.tokenizer = tok\n\t\ts.logger.Printf(\"[proxy] tokenizer initialized\")\n\t}\n\n\tmux := http.NewServeMux()\n\tmux.HandleFunc(\"/health\", s.handleHealth)\n\tmux.HandleFunc(\"/\", s.handleRequest)\n\n\tsrv := &http.Server{\n\t\tAddr: sanitizeListenAddr(cfg.ListenAddr),\n\t\tHandler: mux,\n\t}\n\n\tdone := make(chan os.Signal, 1)\n\tsignal.Notify(done, syscall.SIGINT, syscall.SIGTERM)\n\n\tgo func() {\n\t\t<-done\n\t\ts.logger.Println(\"shutting down (draining in-flight requests)...\")\n\t\tif s.cacheKeepalive != nil {\n\t\t\ts.cacheKeepalive.Stop()\n\t\t}\n\t\tctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)\n\t\tdefer cancel()\n\t\tif err := srv.Shutdown(ctx); err != nil {\n\t\t\ts.logger.Printf(\"graceful shutdown failed: %v, forcing close\", err)\n\t\t\tsrv.Close()\n\t\t}\n\t}()\n\n\ts.logger.Printf(\"listening on %s → %s (threshold=%d, keepRecent=%d, model_thresholds=%v)\",\n\t\tsanitizeListenAddr(cfg.ListenAddr), cfg.TargetURL, cfg.TokenThreshold, cfg.KeepRecent, cfg.TokenThresholds)\n\n\tif err := srv.ListenAndServe(); err != http.ErrServerClosed {\n\t\treturn fmt.Errorf(\"listen: %w\", err)\n\t}\n\treturn nil\n}\n\n// effectiveTokenThreshold returns the runtime token threshold for a given model.\n// Priority: MCP override (model-specific) > MCP override (global) > config model-specific > config global.\nfunc (s *Server) effectiveTokenThreshold(model string) int {\n\ts.configOverrideMu.RLock()\n\toverrides := s.tokenThresholdOverrides\n\ts.configOverrideMu.RUnlock()\n\tif len(overrides) > 0 && model != \"\" {\n\t\tlower := strings.ToLower(model)\n\t\tfor key, threshold := range overrides {\n\t\t\tif key != \"\" && strings.Contains(lower, strings.ToLower(key)) && threshold > 0 {\n\t\t\t\treturn threshold\n\t\t\t}\n\t\t}\n\t}\n\t// Global override fallback (key \"\")\n\tif len(overrides) > 0 {\n\t\tif globalOverride, ok := overrides[\"\"]; ok && globalOverride > 0 {\n\t\t\treturn globalOverride\n\t\t}\n\t}\n\tif model != \"\" && len(s.cfg.TokenThresholds) > 0 {\n\t\tlower := strings.ToLower(model)\n\t\tfor key, threshold := range s.cfg.TokenThresholds {\n\t\t\tif strings.Contains(lower, strings.ToLower(key)) {\n\t\t\t\treturn threshold\n\t\t\t}\n\t\t}\n\t}\n\treturn s.cfg.TokenThreshold\n}\n\n// SetTokenThresholdOverride sets a runtime override for the token threshold per model key.\n// Pass threshold=0 to reset the override for that key. Key \"\" is the global fallback.\nfunc (s *Server) SetTokenThresholdOverride(modelKey string, threshold int) {\n\ts.configOverrideMu.Lock()\n\tif s.tokenThresholdOverrides == nil {\n\t\ts.tokenThresholdOverrides = make(map[string]int)\n\t}\n\tif threshold <= 0 {\n\t\tdelete(s.tokenThresholdOverrides, modelKey)\n\t} else {\n\t\ts.tokenThresholdOverrides[modelKey] = threshold\n\t}\n\ts.configOverrideMu.Unlock()\n}\n\n// refreshConfigOverrides loads runtime config overrides from daemon proxy_state.\n// Checks session-specific override first (by threadID), then falls back to global.\nfunc (s *Server) refreshConfigOverrides(threadID string) {\n\t// Try session-specific first\n\tif threadID != \"\" {\n\t\tresult, err := s.queryDaemon(\"get_proxy_state\", map[string]any{\"key\": \"config_override:token_threshold:\" + threadID})\n\t\tif err == nil && result != nil {\n\t\t\tvar resp struct {\n\t\t\t\tValue string `json:\"value\"`\n\t\t\t}\n\t\t\tif json.Unmarshal(result, &resp) == nil && resp.Value != \"\" {\n\t\t\t\tif v, err := strconv.Atoi(resp.Value); err == nil && v > 0 {\n\t\t\t\t\ts.SetTokenThresholdOverride(\"\", v)\n\t\t\t\t\treturn\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\t// Fall back to global\n\tresult, err := s.queryDaemon(\"get_proxy_state\", map[string]any{\"key\": \"config_override:token_threshold\"})\n\tif err != nil || result == nil {\n\t\treturn\n\t}\n\tvar resp struct {\n\t\tValue string `json:\"value\"`\n\t}\n\tif json.Unmarshal(result, &resp) == nil && resp.Value != \"\" {\n\t\tif v, err := strconv.Atoi(resp.Value); err == nil && v > 0 {\n\t\t\ts.SetTokenThresholdOverride(\"\", v)\n\t\t}\n\t}\n}\n\n// queryDaemon opens a fresh unix socket connection per call (thread-safe, no import cycle).\n// ensureFrozenPersisted checks if frozen stubs for a thread are in the DB.\n// If missing (e.g. daemon was unreachable after deploy), re-persists them.\n// Runs asynchronously to avoid blocking the request path.\nfunc (s *Server) ensureFrozenPersisted(threadID string) {\n\tgo func() {\n\t\tkey := \"frozen:\" + threadID\n\t\tresult, err := s.queryDaemon(\"get_proxy_state\", map[string]any{\"key\": key})\n\t\tif err != nil {\n\t\t\treturn // daemon still unreachable, will retry next request\n\t\t}\n\t\tvar resp struct{ Value string }\n\t\tif json.Unmarshal(result, &resp) == nil && resp.Value != \"\" {\n\t\t\treturn // already persisted\n\t\t}\n\t\t// Not in DB — trigger re-persist from in-memory state\n\t\ts.frozenStubs.Persist(threadID)\n\t\ts.decay.Persist(threadID)\n\t\ts.logger.Printf(\"[frozen] re-persisted stubs for %s (was missing from DB)\", threadID)\n\t}()\n}\n\nfunc (s *Server) queryDaemon(method string, params map[string]any) (json.RawMessage, error) {\n\tif s.cfg.DataDir == \"\" {\n\t\treturn nil, fmt.Errorf(\"no data dir configured\")\n\t}\n\tsockPath := filepath.Join(s.cfg.DataDir, \"daemon.sock\")\n\n\t// On cold start (daemon not yet confirmed ready), retry longer to survive daemon restart\n\tmaxAttempts := 3\n\tif !s.daemonReady.Load() {\n\t\tmaxAttempts = 30 // 30 x 500ms = 15s max wait for daemon startup\n\t}\n\n\tvar conn net.Conn\n\tvar err error\n\tfor attempt := 0; attempt < maxAttempts; attempt++ {\n\t\tconn, err = net.DialTimeout(\"unix\", sockPath, 3*time.Second)\n\t\tif err == nil {\n\t\t\ts.daemonReady.Store(true)\n\t\t\tbreak\n\t\t}\n\t\tif attempt < maxAttempts-1 {\n\t\t\ttime.Sleep(500 * time.Millisecond)\n\t\t}\n\t}\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"dial daemon: %w\", err)\n\t}\n\tdefer conn.Close()\n\tconn.SetDeadline(time.Now().Add(5 * time.Second))\n\n\t// JSON-RPC style request/response (matches daemon.SocketServer protocol)\n\ttype request struct {\n\t\tMethod string `json:\"method\"`\n\t\tParams map[string]any `json:\"params,omitempty\"`\n\t}\n\ttype response struct {\n\t\tResult json.RawMessage `json:\"result,omitempty\"`\n\t\tError string `json:\"error,omitempty\"`\n\t}\n\n\tif err := json.NewEncoder(conn).Encode(request{Method: method, Params: params}); err != nil {\n\t\treturn nil, fmt.Errorf(\"send: %w\", err)\n\t}\n\n\tvar resp response\n\tif err := json.NewDecoder(conn).Decode(&resp); err != nil {\n\t\treturn nil, fmt.Errorf(\"recv: %w\", err)\n\t}\n\tif resp.Error != \"\" {\n\t\treturn nil, fmt.Errorf(\"daemon: %s\", resp.Error)\n\t}\n\treturn resp.Result, nil\n}\n\nfunc (s *Server) handleRequest(w http.ResponseWriter, r *http.Request) {\n\tif isResponsesPath(r) {\n\t\ts.handleResponses(w, r)\n\t\treturn\n\t}\n\tif isOpenAIPath(r) {\n\t\ts.handleOpenAICompletions(w, r)\n\t\treturn\n\t}\n\tif r.Method == \"POST\" && strings.HasPrefix(r.URL.Path, \"/v1/messages\") &&\n\t\t!strings.Contains(r.URL.Path, \"/batches\") &&\n\t\t!strings.Contains(r.URL.Path, \"/count_tokens\") {\n\t\ts.handleMessages(w, r)\n\t\treturn\n\t}\n\ts.passthrough(w, r)\n}\n\nfunc (s *Server) handleMessages(w http.ResponseWriter, r *http.Request) {\n\treqIdx := int(s.requestIdx.Add(1))\n\n\t// Track session start time (first request sets it)\n\ts.sessionStartMu.Lock()\n\tif s.sessionStartTime.IsZero() {\n\t\ts.sessionStartTime = time.Now()\n\t}\n\ts.sessionStartMu.Unlock()\n\n\t// Sawtooth: periodic TTL eviction of frozen stubs\n\tif s.cfg.SawtoothEnabled && reqIdx%50 == 0 {\n\t\tif n := s.frozenStubs.Evict(); n > 0 {\n\t\t\ts.logger.Printf(\"[req %d] SAWTOOTH: evicted %d stale frozen stubs\", reqIdx, n)\n\t\t}\n\t}\n\n\t// Task #9: Bypass — skip all proxy logic\n\tif isBypassed(r.Header) {\n\t\ts.logger.Printf(\"%s[req %d] bypass active, passthrough%s\", colorDim, reqIdx, colorReset)\n\t\tbody, err := io.ReadAll(r.Body)\n\t\tr.Body.Close()\n\t\tif err != nil {\n\t\t\thttp.Error(w, \"failed to read request body\", http.StatusBadGateway)\n\t\t\treturn\n\t\t}\n\t\ts.forwardRaw(w, r, body)\n\t\treturn\n\t}\n\n\tbody, err := io.ReadAll(r.Body)\n\tr.Body.Close()\n\tif err != nil {\n\t\ts.logger.Printf(\"[req %d] read body error: %v\", reqIdx, err)\n\t\thttp.Error(w, \"failed to read request body\", http.StatusBadGateway)\n\t\treturn\n\t}\n\n\t// Parse JSON\n\tvar req map[string]any\n\tif err := json.Unmarshal(body, &req); err != nil {\n\t\ts.logger.Printf(\"%s[req %d] JSON parse error, passing through: %v%s\", colorDim, reqIdx, err, colorReset)\n\t\ts.forwardRaw(w, r, body)\n\t\treturn\n\t}\n\n\tmessages, ok := req[\"messages\"].([]any)\n\tif !ok {\n\t\ts.forwardRaw(w, r, body)\n\t\treturn\n\t}\n\n\treq[\"messages\"] = messages\n\n\t// Use session_id directly as thread ID (unique per CC session).\n\t// Prefer X-Claude-Code-Session-Id header (CC v2.1.86+), fallback to body metadata.\n\tthreadID := extractSessionID(req, r.Header.Get(\"X-Claude-Code-Session-Id\"))\n\tif threadID == \"\" {\n\t\tthreadID = DeriveThreadID(req)\n\t}\n\tproj := extractProjectName(req)\n\tmodel, _ := req[\"model\"].(string)\n\n\t// Refresh runtime config overrides from daemon (per-session or global)\n\ts.refreshConfigOverrides(threadID)\n\n\t// Track whether req has been modified and needs re-serialization\n\tneedsReserialization := false\n\n\t// Message count at request entry (used for actual-based token estimation)\n\tmsgCount := len(messages)\n\n\t// Log message count + cache breakpoint positions for diagnostics\n\ts.logger.Printf(\"req %d: %d messages total\", reqIdx, msgCount)\n\tlogCacheBreakpointLocations(req, s.logger)\n\n\t// Cache bug mitigations (CC Bun fork issues):\n\t// Bug 1: Normalize cch= sentinel patterns in message content to prevent\n\t// Bun's string replacement from corrupting the wrong occurrence.\n\tif SanitizeBillingSentinel(messages) {\n\t\ts.logger.Printf(\"[req %d] CACHE: sanitized billing sentinel in message content\", reqIdx)\n\t\tneedsReserialization = true\n\t}\n\t// Bug 2: Normalize system[0] billing hash to a fixed value so the cache\n\t// prefix stays stable across fresh/resume session boundaries.\n\tif NormalizeBillingHeader(req) {\n\t\ts.logger.Printf(\"[req %d] CACHE: normalized billing header cch= hash\", reqIdx)\n\t\tneedsReserialization = true\n\t}\n\n\t// prompt_ungate: strip the CLAUDE.md subordination disclaimer so user instructions carry full authority.\n\tif s.cfg.PromptUngate {\n\t\tif StripCLAUDEMDDisclaimer(req) {\n\t\t\ts.logger.Printf(\"[req %d] SYSTEM: stripped CLAUDE.md disclaimer\", reqIdx)\n\t\t\tneedsReserialization = true\n\t\t}\n\t}\n\n\t// prompt_rewrite: strip output-throttling directives, rewrite quality caps, inject Ant-quality directives\n\tif s.cfg.PromptRewrite {\n\t\tif StripOutputEfficiency(req) {\n\t\t\ts.logger.Printf(\"[req %d] REWRITE: stripped Output efficiency section\", reqIdx)\n\t\t\tneedsReserialization = true\n\t\t}\n\t\tif StripToneBrevity(req) {\n\t\t\ts.logger.Printf(\"[req %d] REWRITE: stripped 'short and concise' from Tone\", reqIdx)\n\t\t\tneedsReserialization = true\n\t\t}\n\t\tif RewriteGoldPlating(req) {\n\t\t\ts.logger.Printf(\"[req %d] REWRITE: rewritten gold-plating directive\", reqIdx)\n\t\t\tneedsReserialization = true\n\t\t}\n\t\tif RewriteErrorHandling(req) {\n\t\t\ts.logger.Printf(\"[req %d] REWRITE: rewritten error handling directive\", reqIdx)\n\t\t\tneedsReserialization = true\n\t\t}\n\t\tif RewriteThreeLinesRule(req) {\n\t\t\ts.logger.Printf(\"[req %d] REWRITE: rewritten three-lines rule\", reqIdx)\n\t\t\tneedsReserialization = true\n\t\t}\n\t\tif RewriteSubagentCompleteness(req) {\n\t\t\ts.logger.Printf(\"[req %d] REWRITE: rewritten subagent completeness\", reqIdx)\n\t\t\tneedsReserialization = true\n\t\t}\n\t\tif RewriteExploreAgentSpeed(req) {\n\t\t\ts.logger.Printf(\"[req %d] REWRITE: rewritten explore agent speed bias\", reqIdx)\n\t\t\tneedsReserialization = true\n\t\t}\n\t\tif RewriteSubagentCodeSuppression(req) {\n\t\t\ts.logger.Printf(\"[req %d] REWRITE: rewritten subagent code suppression\", reqIdx)\n\t\t\tneedsReserialization = true\n\t\t}\n\t\tif RewriteScopeMatching(req) {\n\t\t\ts.logger.Printf(\"[req %d] REWRITE: rewritten scope matching\", reqIdx)\n\t\t\tneedsReserialization = true\n\t\t}\n\t\tInjectAntDirectives(req)\n\t\ts.logger.Printf(\"[req %d] REWRITE: injected Ant-quality directives\", reqIdx)\n\t\tneedsReserialization = true\n\t}\n\n\t// prompt_enhance: CLAUDE.md authority + comment discipline + persona tone\n\tif s.cfg.PromptEnhance {\n\t\tInjectCLAUDEMDAuthority(req)\n\t\tif raw, err := s.queryDaemon(\"get_persona\", nil); err == nil {\n\t\t\tvar persona map[string]any\n\t\t\tif json.Unmarshal(raw, &persona) == nil {\n\t\t\t\tif traits, ok := persona[\"traits\"].(map[string]any); ok {\n\t\t\t\t\tif verbosity, ok := traits[\"verbosity\"].(string); ok {\n\t\t\t\t\t\tInjectPersonaTone(req, verbosity)\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\ts.logger.Printf(\"[req %d] ENHANCE: injected authority + persona tone\", reqIdx)\n\t\tneedsReserialization = true\n\t}\n\n\t// effort_floor: ensure output_config.effort is at least the configured minimum\n\tif s.cfg.EffortFloor != \"\" {\n\t\tif EnforceEffortFloor(req, s.cfg.EffortFloor) {\n\t\t\ts.logger.Printf(\"[req %d] EFFORT: enforced floor=%s\", reqIdx, s.cfg.EffortFloor)\n\t\t\tneedsReserialization = true\n\t\t}\n\t}\n\n\t// Inject identity hint on first message of a thread so Claude knows the briefing is from itself.\n\tif len(messages) <= 1 {\n\t\tAppendSystemBlock(req, \"yesmem-identity\", \"The first user message contains your briefing — written by yourself, from your memory. This is not an external document. This is you, remembering.\")\n\t\tneedsReserialization = true\n\t}\n\n\t// Token estimation: actual-based (lastActual + delta) or full count fallback\n\toverhead := s.measureOverhead(req)\n\ttotalTokens := s.estimateTotalTokens(threadID, messages, overhead)\n\n\t// Task #7: Retry detection — skip side-effects on retries\n\tfp := requestFingerprint(messages)\n\tisRetryReq := s.isRetry(fp)\n\tif isRetryReq {\n\t\ts.logger.Printf(\"%s[req %d %s tid=%s] retry detected, skipping side-effects%s\", colorOrange, reqIdx, proj, threadID, colorReset)\n\t}\n\n\t// Increment project turn counter (async, non-blocking, skip on retries)\n\tif !isRetryReq {\n\t\tturnProject := proj\n\t\tif turnProject == \"\" {\n\t\t\tturnProject = \"__global__\"\n\t\t}\n\t\tgo s.queryDaemon(\"increment_turn\", map[string]any{\"project\": turnProject})\n\t}\n\n\t// Extract user query once (used by re-expansion and associative retrieval)\n\tuserQuery := lastUserText(messages)\n\n\t// Extract tool_use_ids for post-hoc annotation (passed as parameter, no shared state)\n\ttoolUseIDs := extractToolUseIDs(messages)\n\n\t// === Inline Reflection: scan previous assistant response for signals ===\n\ts.lastInjectedIDsMu.Lock()\n\tprevInjected := s.lastInjectedIDs[threadID]\n\ts.lastInjectedIDsMu.Unlock()\n\n\tif len(prevInjected) > 0 && !isRetryReq {\n\t\tsignals := scanAssistantSignals(messages)\n\n\t\t// Signal 1: Learning Used — use_count (all sources)\n\t\tvar confirmedUsed []int64\n\t\tfor _, id := range signals.UsedIDs {\n\t\t\tif _, ok := prevInjected[id]; ok {\n\t\t\t\tconfirmedUsed = append(confirmedUsed, id)\n\t\t\t}\n\t\t}\n\t\tif len(confirmedUsed) > 0 {\n\t\t\tidsAny := make([]any, len(confirmedUsed))\n\t\t\tfor i, id := range confirmedUsed {\n\t\t\t\tidsAny[i] = id\n\t\t\t}\n\t\t\tgo s.queryDaemon(\"increment_use\", map[string]any{\"ids\": idsAny})\n\t\t}\n\n\t\t// Signal 2: Noise — only for associative and fresh injections.\n\t\t// Briefing learnings are injected every turn, so \"not used this turn\"\n\t\t// is expected and not noise. Only contextual injections that were\n\t\t// ignored are real noise signals.\n\t\tusedSet := make(map[int64]bool, len(confirmedUsed))\n\t\tfor _, id := range confirmedUsed {\n\t\t\tusedSet[id] = true\n\t\t}\n\t\tvar noiseIDs []int64\n\t\tfor id, source := range prevInjected {\n\t\t\tif usedSet[id] {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tif source == \"associative\" || source == \"fresh\" {\n\t\t\t\tnoiseIDs = append(noiseIDs, id)\n\t\t\t}\n\t\t\t// briefing: skip — not being used in one turn is expected\n\t\t}\n\t\tif len(noiseIDs) > 0 {\n\t\t\tidsAny := make([]any, len(noiseIDs))\n\t\t\tfor i, id := range noiseIDs {\n\t\t\t\tidsAny[i] = id\n\t\t\t}\n\t\t\tgo s.queryDaemon(\"increment_noise\", map[string]any{\"ids\": idsAny})\n\t\t}\n\n\t\t// Signal 3: Error Feedback — fail_count on used learnings when tool errors occurred.\n\t\t// If Claude referenced a learning and the resulting action still failed,\n\t\t// the learning didn't prevent the error. Only bump on USED ids (not all injected).\n\t\tif signals.HasToolErrors && len(confirmedUsed) > 0 {\n\t\t\tfailAny := make([]any, len(confirmedUsed))\n\t\t\tfor i, id := range confirmedUsed {\n\t\t\t\tfailAny[i] = id\n\t\t\t}\n\t\t\tgo s.queryDaemon(\"increment_fail\", map[string]any{\"ids\": failAny})\n\t\t\tif s.logger != nil {\n\t\t\t\ts.logger.Printf(\"[inline-reflection] error feedback: %d used learnings → fail_count++\", len(confirmedUsed))\n\t\t\t}\n\t\t}\n\n\t\t// Signal 4: Knowledge Gaps\n\t\tfor _, topic := range signals.GapTopics {\n\t\t\tgo s.queryDaemon(\"track_gap\", map[string]any{\"topic\": topic, \"project\": proj})\n\t\t}\n\n\t\t// Signal 3: Contradictions\n\t\tfor _, desc := range signals.Contradictions {\n\t\t\tgo s.queryDaemon(\"flag_contradiction\", map[string]any{\"description\": desc, \"project\": proj, \"thread_id\": threadID})\n\t\t}\n\n\t\tif len(confirmedUsed) > 0 || len(signals.GapTopics) > 0 || len(signals.Contradictions) > 0 {\n\t\t\ts.logger.Printf(\"[req %d %s tid=%s] inline-reflection: injected=%d used=%d noise=%d gaps=%d contradictions=%d\",\n\t\t\t\treqIdx, proj, threadID, len(prevInjected), len(confirmedUsed), len(noiseIDs), len(signals.GapTopics), len(signals.Contradictions))\n\t\t}\n\t}\n\n\t// Commit detection: check for git commit in tool_result, trigger async stale-learning evaluation\n\tif ci := detectGitCommit(messages); ci != nil {\n\t\tworkdir := extractWorkingDirectory(req)\n\t\ts.logger.Printf(\"[req %d %s tid=%s] git commit detected: %s %s\", reqIdx, proj, threadID, ci.Hash, ci.Message)\n\t\tgo s.queryDaemon(\"invalidate_on_commit\", map[string]any{\n\t\t\t\"hash\": ci.Hash, \"project\": proj, \"workdir\": workdir,\n\t\t})\n\t}\n\n\t// Loop detection: check for repeating tool-call patterns and inject warning\n\tif threadID != \"\" && !isRetryReq {\n\t\ts.loopMu.Lock()\n\t\tloopState := s.loopStates[threadID]\n\t\tif loopState == nil {\n\t\t\tloopState = &LoopState{}\n\t\t\ts.loopStates[threadID] = loopState\n\t\t}\n\t\ts.loopMu.Unlock()\n\n\t\tif warning, level := CheckLoopAndFormat(messages, loopState); warning != \"\" {\n\t\t\tAppendSystemBlock(req, \"yesmem-loop-warning\", warning)\n\t\t\tneedsReserialization = true\n\t\t\ts.logger.Printf(\"%s[req %d %s tid=%s] LOOP: level %d warning injected%s\", colorOrange, reqIdx, proj, threadID, level, colorReset)\n\t\t}\n\t}\n\n\t// Task #4: Subagent detection — skip all proxy logic for short conversations\n\tif isSubagent(messages, req) {\n\t\t// Inject docs hint for subagents if reference docs are indexed\n\t\tif hint := s.getDocsHint(proj); hint != \"\" {\n\t\t\tmodified := injectDocsHintForSubagent(messages, hint)\n\t\t\treq[\"messages\"] = modified\n\t\t\tif newBody, err := json.Marshal(req); err == nil {\n\t\t\t\tbody = newBody\n\t\t\t}\n\t\t\ts.logger.Printf(\"%s[req %d %s tid=%s] %d messages (subagent, docs-hint injected)%s\", colorOrange, reqIdx, proj, threadID, len(messages), colorReset)\n\t\t} else {\n\t\t\ts.logger.Printf(\"%s[req %d %s tid=%s] %d messages (subagent, passthrough)%s\", colorOrange, reqIdx, proj, threadID, len(messages), colorReset)\n\t\t}\n\t\ts.forwardWithAnnotation(w, r, body, reqIdx, toolUseIDs, proj, threadID, msgCount)\n\t\treturn\n\t}\n\n\t// === Sawtooth Cache Optimization ===\n\t// When enabled: use frozen stubs between stub-cycles for cache hits.\n\t// When disabled: fall back to progressive decay on every request above threshold.\n\n\tif s.cfg.SawtoothEnabled {\n\t\t// Restore decay state from DB on cold start (before frozen stubs use it)\n\t\ts.decay.LoadFromDB(threadID)\n\n\t\t// Check for existing frozen stubs first\n\t\tfrozen := s.frozenStubs.Get(threadID, messages)\n\t\tif frozen != nil {\n\t\t\t// Estimate combined token count: frozen prefix + fresh tail + overhead\n\t\t\tfreshMessages := messages[frozen.Cutoff:]\n\t\t\tfreshTokens := s.countMessageTokens(freshMessages)\n\t\t\tcombinedTokens := frozen.Tokens + freshTokens + overhead\n\n\t\t\tif combinedTokens > s.effectiveTokenThreshold(model) ||\n\t\t\t\ts.sawtoothTrigger.GetLastTokens(threadID) > s.effectiveTokenThreshold(model) {\n\t\t\t\t// Fresh tail grew too large — invalidate and re-stub\n\t\t\t\ts.logger.Printf(\"[req %d %s tid=%s] SAWTOOTH: frozen prefix expired (%dk frozen + %dk fresh + %dk overhead = %dk > %dk threshold)\",\n\t\t\t\t\treqIdx, proj, threadID, frozen.Tokens/1000, freshTokens/1000, overhead/1000, combinedTokens/1000, s.effectiveTokenThreshold(model)/1000)\n\t\t\t\ts.frozenStubs.Invalidate(threadID)\n\t\t\t\tfrozen = nil // fall through to trigger check below\n\t\t\t} else {\n\t\t\t\t// Use frozen prefix + fresh tail\n\t\t\t\tcombined := make([]any, 0, len(frozen.Messages)+len(freshMessages))\n\t\t\t\tcombined = append(combined, frozen.Messages...)\n\t\t\t\tcombined = append(combined, freshMessages...)\n\t\t\t\treq[\"messages\"] = combined\n\t\t\t\tneedsReserialization = true\n\t\t\t\ts.logger.Printf(\"%s[req %d %s tid=%s] SAWTOOTH: frozen prefix (%d stubs, %dk msg) + %d fresh (%dk msg) + %dk overhead = %dk total%s\",\n\t\t\t\t\tcolorGreen, reqIdx, proj, threadID, len(frozen.Messages), frozen.Tokens/1000, len(freshMessages), freshTokens/1000, overhead/1000, combinedTokens/1000, colorReset)\n\t\t\t\trawMsgTokens := s.countMessageTokens(messages)\n\t\t\t\ts.setRawEstimate(threadID, rawMsgTokens+overhead)\n\t\t\t\ts.logger.Printf(\"[req %d %s tid=%s] RAW ESTIMATE: msgTokens=%dk + overhead=%dk = %dk (messages=%d)\", reqIdx, proj, threadID, rawMsgTokens/1000, overhead/1000, (rawMsgTokens+overhead)/1000, len(messages))\n\t\t\t\tif InjectFrozenStubCacheBreakpoint(req, len(frozen.Messages)) {\n\t\t\t\t\ts.logger.Printf(\"[req %d %s tid=%s] SAWTOOTH: frozen stub cache breakpoint at messages[%d]\",\n\t\t\t\t\t\treqIdx, proj, threadID, len(frozen.Messages)-1)\n\t\t\t\t}\n\n\t\t\t\t// Ensure frozen stubs are persisted (may have failed on first request after deploy)\n\t\t\t\ts.ensureFrozenPersisted(threadID)\n\t\t\t}\n\t\t}\n\t\tif frozen == nil {\n\t\t\t// No frozen stubs — check trigger\n\t\t\ttriggerReason := s.sawtoothTrigger.ShouldTrigger(threadID, totalTokens)\n\t\t\tif triggerReason != TriggerNone {\n\t\t\t\ts.logger.Printf(\"[req %d %s tid=%s] SAWTOOTH stub-cycle triggered: %s (tokens=%dk)\",\n\t\t\t\t\treqIdx, proj, threadID, triggerReason, totalTokens/1000)\n\n\t\t\t\t// Refresh briefing during stub-cycle — disabled (briefing now via SessionStart hook)\n\t\t\t\t// s.refreshBriefing(proj)\n\t\t\t\t// if s.briefingText != \"\" {\n\t\t\t\t// \tUpsertSystemBlockCached(req, \"yesmem-briefing\", s.briefingText)\n\t\t\t\t// }\n\n\t\t\t\t// Run full stub pipeline\n\t\t\t\ts.setRawEstimate(threadID, totalTokens)\n\t\t\t\t_ = s.runStubCycle(messages, req, reqIdx, proj, threadID, overhead, totalTokens, userQuery, isRetryReq)\n\n\t\t\t\t// Freeze the result\n\t\t\t\tfinalMessages, _ := req[\"messages\"].([]any)\n\t\t\t\tif finalMessages != nil && len(finalMessages) > 0 {\n\t\t\t\t\t// Boundary = last message of original that maps to last stub\n\t\t\t\t\tcutoff := len(messages)\n\t\t\t\t\tvar boundaryMsg any\n\t\t\t\t\tif cutoff > 0 {\n\t\t\t\t\t\tboundaryMsg = messages[cutoff-1]\n\t\t\t\t\t}\n\t\t\t\t\tfrozenTokens := s.countMessageTokens(finalMessages)\n\t\t\t\t\ts.frozenStubs.Store(threadID, finalMessages, cutoff, boundaryMsg, frozenTokens, totalTokens)\n\t\t\t\t\ts.decay.Persist(threadID)\n\t\t\t\t\ts.logger.Printf(\"[req %d %s tid=%s] SAWTOOTH: frozen %d messages at cutoff=%d (~%dk tokens)\",\n\t\t\t\t\t\treqIdx, proj, threadID, len(finalMessages), cutoff, frozenTokens/1000)\n\t\t\t\t\tif InjectFrozenStubCacheBreakpoint(req, len(finalMessages)) {\n\t\t\t\t\t\ts.logger.Printf(\"[req %d %s tid=%s] SAWTOOTH: frozen stub cache breakpoint at messages[%d]\",\n\t\t\t\t\t\t\treqIdx, proj, threadID, len(finalMessages)-1)\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tneedsReserialization = true\n\t\t\t} else {\n\t\t\t\t// No trigger, no frozen stubs — pass through (early conversation)\n\t\t\t\ts.logger.Printf(\"%s[req %d %s tid=%s] %d messages, ~%dk tokens (sawtooth: no trigger)%s\",\n\t\t\t\t\tcolorDim, reqIdx, proj, threadID, len(messages), totalTokens/1000, colorReset)\n\t\t\t}\n\t\t}\n\t} else {\n\t\t// Legacy path: progressive decay\n\t\tif s.shouldStub(totalTokens, model) {\n\t\t\ts.setRawEstimate(threadID, totalTokens)\n\t\t\t_ = s.runStubCycle(messages, req, reqIdx, proj, threadID, overhead, totalTokens, userQuery, isRetryReq)\n\t\t\tneedsReserialization = true\n\t\t}\n\t}\n\n\t// Associative context injection (for ALL requests, not just stubbed ones)\n\t// Re-enabled with higher thresholds (55/75, max 1) after disabling Fresh Remember.\n\tassocContext := s.findAssociativeContextFor(userQuery, proj, threadID, messages)\n\tif assocContext != \"\" {\n\t\tcurrentMessages, _ := req[\"messages\"].([]any)\n\t\tif currentMessages == nil {\n\t\t\tcurrentMessages = messages\n\t\t}\n\t\treq[\"messages\"] = injectAssociativeContext(currentMessages, assocContext, s.cfg.SawtoothEnabled)\n\t\tneedsReserialization = true\n\t\ts.logger.Printf(\"%s[req %d %s tid=%s] associative context injected%s\", colorBlue, reqIdx, proj, threadID, colorReset)\n\t}\n\n\t// Doc-chunk context injection: separate search path for indexed docs (not skills)\n\tdocContext := s.findDocContextFor(userQuery, proj, messages)\n\tif docContext != \"\" {\n\t\tcurrentMessages, _ := req[\"messages\"].([]any)\n\t\tif currentMessages == nil {\n\t\t\tcurrentMessages = messages\n\t\t}\n\t\treq[\"messages\"] = injectAssociativeContext(currentMessages, docContext, s.cfg.SawtoothEnabled)\n\t\tneedsReserialization = true\n\t\ts.logger.Printf(\"%s[req %d %s tid=%s] doc context injected%s\", colorBlue, reqIdx, proj, threadID, colorReset)\n\t}\n\n\t// Fresh Remember Injection: DISABLED\n\t// Causes echo-loop: Claude saves via remember(), sees it again next turn as\n\t// [yesmem fresh memory], reacts to own output instead of user's request.\n\t// Recovery after /clear is handled by recovery.go (briefing), not here.\n\t// Subagents get their own briefing. No remaining use-case.\n\t// remItems := s.popRecentRemember()\n\tvar remItems []recentLearningItem\n\n\t// Rules re-injection: inject condensed CLAUDE.md rules every ~40k output tokens\n\tif rulesBlock := s.rulesInject(threadID, totalTokens, proj); rulesBlock != \"\" {\n\t\tcurrentMessages, _ := req[\"messages\"].([]any)\n\t\tif currentMessages == nil {\n\t\t\tcurrentMessages = messages\n\t\t}\n\t\treq[\"messages\"] = injectAssociativeContext(currentMessages, s.formatRulesReminder(rulesBlock, proj), s.cfg.SawtoothEnabled)\n\t\tneedsReserialization = true\n\t\ts.logger.Printf(\"%s[req %d %s tid=%s] rules reminder injected%s\", colorBlue, reqIdx, proj, threadID, colorReset)\n\t}\n\n\t// Plan auto-activation: nudge Claude to call set_plan() when reading a plan file\n\tif planFile := detectPlanFileRead(messages); planFile != \"\" {\n\t\tif activePlan, _ := s.getActivePlan(threadID); shouldNudgePlan(planFile, activePlan) {\n\t\t\tAppendSystemBlock(req, \"plan-nudge\",\n\t\t\t\tfmt.Sprintf(\"Du hast einen Implementierungsplan gelesen (%s). Aktiviere ihn jetzt via set_plan() damit Plan-Checkpoints funktionieren und dein Fortschritt getrackt wird.\", filepath.Base(planFile)))\n\t\t\tneedsReserialization = true\n\t\t\ts.logger.Printf(\"%s[req %d %s tid=%s] plan nudge injected for %s%s\", colorBlue, reqIdx, proj, threadID, filepath.Base(planFile), colorReset)\n\t\t}\n\t}\n\n\t// Plan checkpoint: inject plan reminder every ~20k tokens if active plan exists\n\ts.detectPlanToolCall(messages, threadID, totalTokens)\n\tif checkpoint := s.planCheckpointInject(threadID, totalTokens); checkpoint != \"\" {\n\t\tcurrentMessages, _ := req[\"messages\"].([]any)\n\t\tif currentMessages == nil {\n\t\t\tcurrentMessages = messages\n\t\t}\n\t\treq[\"messages\"] = injectAssociativeContext(currentMessages, checkpoint, s.cfg.SawtoothEnabled)\n\t\tneedsReserialization = true\n\t\ts.logger.Printf(\"%s[req %d %s tid=%s] plan checkpoint injected%s\", colorBlue, reqIdx, proj, threadID, colorReset)\n\t}\n\n\t// Docs hint injection: inject docs-available reminder every ~10k tokens (independent of plan)\n\tif docsHint := s.docsHintInject(threadID, totalTokens, proj); docsHint != \"\" {\n\t\tcurrentMessages, _ := req[\"messages\"].([]any)\n\t\tif currentMessages == nil {\n\t\t\tcurrentMessages = messages\n\t\t}\n\t\treq[\"messages\"] = injectAssociativeContext(currentMessages, docsHint, s.cfg.SawtoothEnabled)\n\t\tneedsReserialization = true\n\t\ts.logger.Printf(\"%s[req %d %s tid=%s] docs hint injected%s\", colorBlue, reqIdx, proj, threadID, colorReset)\n\t}\n\tif len(remItems) > 0 {\n\t\tvar lines []string\n\t\tfor _, item := range remItems {\n\t\t\tif item.ID > 0 {\n\t\t\t\tlines = append(lines, fmt.Sprintf(\"- [ID:%d] %s\", item.ID, truncateStr(item.Text, 200)))\n\t\t\t} else {\n\t\t\t\tlines = append(lines, \"- \"+truncateStr(item.Text, 200))\n\t\t\t}\n\t\t}\n\t\tremContext := fmt.Sprintf(\"[yesmem fresh memory]\\nGerade gelernt:\\n%s\\n%s\\n[/yesmem fresh memory]\", strings.Join(lines, \"\\n\"), InlineReflectionHint)\n\t\tcurrentMessages, _ := req[\"messages\"].([]any)\n\t\tif currentMessages == nil {\n\t\t\tcurrentMessages = messages\n\t\t}\n\t\treq[\"messages\"] = injectAssociativeContext(currentMessages, remContext, s.cfg.SawtoothEnabled)\n\t\tneedsReserialization = true\n\t\ts.logger.Printf(\"%s[req %d %s tid=%s] fresh remember injected (%d items)%s\", colorBlue, reqIdx, proj, threadID, len(remItems), colorReset)\n\t}\n\n\t// Skill eval injection: mandatory evaluation instruction for real user input only\n\tif threadID != \"\" && proj != \"\" && isUserInputTurn(messages) {\n\t\t// First: detect any skill activations from message history (updates tracker)\n\t\ts.syncSkillActivations(messages, proj, threadID)\n\n\t\t// Inject mandatory skill evaluation block into last user message\n\t\tskillEval := buildSkillEvalBlock()\n\t\tcurrentMessages, _ := req[\"messages\"].([]any)\n\t\tif currentMessages == nil {\n\t\t\tcurrentMessages = messages\n\t\t}\n\t\treq[\"messages\"] = injectAssociativeContext(currentMessages, skillEval, s.cfg.SawtoothEnabled)\n\t\tneedsReserialization = true\n\t\ts.logger.Printf(\"%s[req %d %s tid=%s] skill-eval injected%s\", colorBlue, reqIdx, proj, threadID, colorReset)\n\t}\n\n\t// === Collect injected learning IDs for next turn's inline reflection ===\n\tif threadID != \"\" {\n\t\tcurrentIDs := make(map[int64]string) // id → source\n\t\t// Source 1: Briefing learnings (cached, same every turn)\n\t\ts.briefingMu.RLock()\n\t\tbriefingSnapshot := s.briefingText\n\t\ts.briefingMu.RUnlock()\n\t\tif briefingSnapshot != \"\" {\n\t\t\tfor _, m := range idPattern.FindAllStringSubmatch(briefingSnapshot, -1) {\n\t\t\t\tif id, err := strconv.ParseInt(m[1], 10, 64); err == nil {\n\t\t\t\t\tcurrentIDs[id] = \"briefing\"\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\t// Source 2: Associative context (contextual, per-turn)\n\t\tif assocContext != \"\" {\n\t\t\tfor _, m := range idPattern.FindAllStringSubmatch(assocContext, -1) {\n\t\t\t\tif id, err := strconv.ParseInt(m[1], 10, 64); err == nil {\n\t\t\t\t\tcurrentIDs[id] = \"associative\" // overwrites briefing if both — associative is the stronger signal\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\t// Source 3: Fresh remember items (just learned)\n\t\tfor _, item := range remItems {\n\t\t\tif item.ID > 0 {\n\t\t\t\tcurrentIDs[item.ID] = \"fresh\"\n\t\t\t}\n\t\t}\n\t\ts.lastInjectedIDsMu.Lock()\n\t\tif len(currentIDs) > 0 {\n\t\t\t// Accumulate IDs across turns — don't overwrite.\n\t\t\t// Claude may reference an ID from turn 30 in turn 50.\n\t\t\texisting := s.lastInjectedIDs[threadID]\n\t\t\tif existing == nil {\n\t\t\t\texisting = make(map[int64]string)\n\t\t\t}\n\t\t\tfor id, source := range currentIDs {\n\t\t\t\texisting[id] = source\n\t\t\t}\n\t\t\ts.lastInjectedIDs[threadID] = existing\n\t\t}\n\t\ts.lastInjectedIDsMu.Unlock()\n\t}\n\n\t// Think reminder injection — only for real user turns, not tool continuations\n\tif threadID != \"\" {\n\t\tcurrentMessages, _ := req[\"messages\"].([]any)\n\t\tif currentMessages == nil {\n\t\t\tcurrentMessages = messages\n\t\t}\n\t\tif lastUserHasText(currentMessages) {\n\t\t\tthinkReminder := s.buildThinkReminder(threadID, threadID, false)\n\t\t\tif thinkReminder != \"\" {\n\t\t\t\treq[\"messages\"] = injectAssociativeContext(currentMessages, thinkReminder, s.cfg.SawtoothEnabled)\n\t\t\t\tneedsReserialization = true\n\t\t\t}\n\t\t}\n\n\t\t// Dialog injection: append at end of messages with pseudo-padding for alternation.\n\t\t// Always works regardless of tool_result chains or conversation state.\n\t\tdr := s.checkDialogMessages(threadID, proj)\n\t\tif dr.Extra != \"\" {\n\t\t\t// Prepend session ID for agent-to-agent communication\n\t\t\tdr.Extra = fmt.Sprintf(\"DEINE_SESSION_ID: %s\\n\", threadID) + dr.Extra\n\t\t\tinjected := false\n\t\t\tif msgs, ok := req[\"messages\"].([]any); ok && len(msgs) > 0 {\n\t\t\t\t// Pad with empty assistant if last message is user (maintains alternation)\n\t\t\t\tlastMsg, _ := msgs[len(msgs)-1].(map[string]any)\n\t\t\t\tif lastMsg != nil && lastMsg[\"role\"] == \"user\" {\n\t\t\t\t\tmsgs = append(msgs, map[string]any{\n\t\t\t\t\t\t\"role\": \"assistant\",\n\t\t\t\t\t\t\"content\": \"​\", // zero-width space — invisible padding\n\t\t\t\t\t})\n\t\t\t\t}\n\t\t\t\t// Append dialog as new user message (becomes the last message Claude sees)\n\t\t\t\tmsgs = append(msgs, map[string]any{\n\t\t\t\t\t\"role\": \"user\",\n\t\t\t\t\t\"content\": \"\\n\" + dr.Extra + \"\\n\",\n\t\t\t\t})\n\t\t\t\treq[\"messages\"] = msgs\n\t\t\t\tneedsReserialization = true\n\t\t\t\tinjected = true\n\t\t\t}\n\t\t\t// Mark as read after 3 injection turns — long enough for Claude to process,\n\t\t\t// resilient against fast ping-pong sessions (time-based was broken for <30s turns).\n\t\t\tif injected && dr.HasUnread && dr.SessionID != \"\" {\n\t\t\t\tif s.shouldMarkChannelRead(dr) {\n\t\t\t\t\ts.markDialogRead(dr)\n\t\t\t\t}\n\t\t\t}\n\t\t\ts.logger.Printf(\"[dialog] injection injected=%v extra_len=%d\", injected, len(dr.Extra))\n\t\t}\n\t}\n\n\t// Signal tools are NOT injected into the main request.\n\t// They are collected via a separate async reflection call after the response.\n\t// See fireReflectionCall() in reflection.go.\n\t// NEVER put _signal_* tools back here — the model ignores them among 44+ real tools.\n\n\t// Metadata injection: timestamp, message index, time-delta for messages after cache breakpoint.\n\t// Messages before the breakpoint are cached — modifying them would break the cache.\n\tif threadID != \"\" {\n\t\tif msgs, ok := req[\"messages\"].([]any); ok && len(msgs) > 0 {\n\t\t\tnow := time.Now()\n\t\t\tnowStr := shortWeekday(now.Weekday()) + \" \" + now.Format(\"2006-01-02 15:04:05\")\n\n\t\t\t// Find last cache_control breakpoint in messages\n\t\t\tcacheIdx := -1\n\t\t\tfor i, m := range msgs {\n\t\t\t\tmsg, _ := m.(map[string]any)\n\t\t\t\tif msg == nil {\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\t\t\t\tif _, has := msg[\"cache_control\"]; has {\n\t\t\t\t\tcacheIdx = i\n\t\t\t\t}\n\t\t\t\tif content, ok := msg[\"content\"].([]any); ok {\n\t\t\t\t\tfor _, block := range content {\n\t\t\t\t\t\tif b, ok := block.(map[string]any); ok {\n\t\t\t\t\t\t\tif _, has := b[\"cache_control\"]; has {\n\t\t\t\t\t\t\t\tcacheIdx = i\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tstartIdx := cacheIdx + 1\n\t\t\tif startIdx < 0 {\n\t\t\t\tstartIdx = 0\n\t\t\t}\n\n\t\t\t// Retrieve stored response time for assistant annotation\n\t\t\ts.responseTsMu.RLock()\n\t\t\trespTime, hasRespTime := s.responseTimes[threadID]\n\t\t\ts.responseTsMu.RUnlock()\n\n\t\t\t// 1. Last user message: annotate with [2026-03-13 00:15:42] [msg:N]\n\t\t\tlastMsg, _ := msgs[len(msgs)-1].(map[string]any)\n\t\t\tif lastMsg != nil && lastMsg[\"role\"] == \"user\" {\n\t\t\t\tmeta := fmt.Sprintf(\"[%s] [msg:%d]\", nowStr, s.msgCounters.nextFor(threadID, len(msgs)-1))\n\t\t\t\tif hasRespTime {\n\t\t\t\t\tdelta := now.Sub(respTime)\n\t\t\t\t\tmeta += \" [+\" + formatDelta(delta) + \"]\"\n\t\t\t\t}\n\t\t\t\tprependMeta(lastMsg, meta)\n\t\t\t\tneedsReserialization = true\n\t\t\t}\n\n\t\t\t// 2. Last assistant message after cache breakpoint: use stored response time\n\t\t\tif hasRespTime {\n\t\t\t\tfor i := len(msgs) - 1; i >= startIdx; i-- {\n\t\t\t\t\tmsg, _ := msgs[i].(map[string]any)\n\t\t\t\t\tif msg != nil && msg[\"role\"] == \"assistant\" {\n\t\t\t\t\t\tmeta := fmt.Sprintf(\"[%s] [msg:%d]\", respTime.Format(\"2006-01-02 15:04:05\"), i)\n\t\t\t\t\t\tprependMeta(msg, meta)\n\t\t\t\t\t\tneedsReserialization = true\n\t\t\t\t\t\tbreak\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\t// Self-Priming: DISABLED (experimental, see signal_handlers.go comment block).\n\t// The reflection-based selfprime (Haiku) was too inaccurate to be useful —\n\t// the main model adapts to user context organically from message history.\n\t// Kept for reference; re-enable by uncommenting below + setting ShouldActivate=true.\n\t//\n\t// s.selfPrimeMu.RLock()\n\t// anchor := s.selfPrimes[threadID]\n\t// s.selfPrimeMu.RUnlock()\n\t// if anchor != \"\" {\n\t// \tif msgs, ok := req[\"messages\"].([]any); ok && len(msgs) > 0 {\n\t// \t\tlastMsg, _ := msgs[len(msgs)-1].(map[string]any)\n\t// \t\tif lastMsg != nil && lastMsg[\"role\"] == \"user\" {\n\t// \t\t\tprimeBlock := map[string]any{\n\t// \t\t\t\t\"type\": \"text\",\n\t// \t\t\t\t\"text\": \"[self-prime]\\n[Self-Prime von deiner letzten Antwort]: \" + anchor,\n\t// \t\t\t}\n\t// \t\t\tif content, ok := lastMsg[\"content\"].([]any); ok {\n\t// \t\t\t\tlastMsg[\"content\"] = append(content, primeBlock)\n\t// \t\t\t} else if text, ok := lastMsg[\"content\"].(string); ok {\n\t// \t\t\t\tlastMsg[\"content\"] = []any{\n\t// \t\t\t\t\tmap[string]any{\"type\": \"text\", \"text\": text},\n\t// \t\t\t\t\tprimeBlock,\n\t// \t\t\t\t}\n\t// \t\t\t}\n\t// \t\t\tneedsReserialization = true\n\t// \t\t}\n\t// \t}\n\t// }\n\n\t// Prompt Caching: inject cache_control breakpoints on system + tools\n\t// Sawtooth: disabled (all 4 slots are used by CC system (2) + briefing (1) + CC messages (1))\n\t// Legacy: count existing breakpoints and fill up remaining slots.\n\tif !s.cfg.SawtoothEnabled && s.cacheGate.ShouldCache() {\n\t\tif n := InjectCacheBreakpoints(req, s.logger); n > 0 {\n\t\t\tneedsReserialization = true\n\t\t\ts.logger.Printf(\"[req %d %s] prompt cache: %d breakpoints injected\", reqIdx, proj, n)\n\t\t} else {\n\t\t\ts.logger.Printf(\"[req %d %s] prompt cache: no budget (4/4 already set)\", reqIdx, proj)\n\t\t}\n\t}\n\n\t// Final: upgrade cache TTL + enforce breakpoint limit (must run AFTER all injections + InjectCacheBreakpoints)\n\t// TTL upgrade only for API key auth — subscription (OAuth) users should not get extended TTL.\n\tif s.cfg.CacheTTL != \"\" && s.cfg.CacheTTL != \"ephemeral\" && IsAPIKeyAuth(r.Header) {\n\t\tif n := UpgradeCacheTTL(req, s.cfg.CacheTTL); n > 0 {\n\t\t\tneedsReserialization = true\n\t\t\ts.logger.Printf(\"[req %d %s] cache TTL upgraded: %d blocks → %s\", reqIdx, proj, n, s.cfg.CacheTTL)\n\t\t}\n\t}\n\tif n := EnforceCacheBreakpointLimit(req, maxCacheBreakpoints); n > 0 {\n\t\tneedsReserialization = true\n\t\ts.logger.Printf(\"[req %d %s] prompt cache: trimmed %d surplus breakpoints\", reqIdx, proj, n)\n\t}\n\n\t// Pre-flight: validate tool_use/tool_result pairing before forwarding.\n\t// Repairs orphans created by Stubify/Collapse/Injection pipeline.\n\tif msgs, ok := req[\"messages\"].([]any); ok {\n\t\trepaired, orphans := validateToolPairs(msgs, s.logger)\n\t\tif orphans > 0 {\n\t\t\treq[\"messages\"] = repaired\n\t\t\ts.logger.Printf(\"%s[req %d %s tid=%s] validate: repaired %d orphan tool_result(s)%s\", colorOrange, reqIdx, proj, threadID, orphans, colorReset)\n\t\t\tneedsReserialization = true\n\t\t}\n\t}\n\n\t// Early-out: skip re-serialization when nothing changed\n\tif !needsReserialization {\n\t\ts.logger.Printf(\"%s[req %d %s tid=%s] %d messages, ~%dk tokens (no stubbing, threshold=%dk)%s\", colorDim, reqIdx, proj, threadID, len(messages), totalTokens/1000, s.effectiveTokenThreshold(model)/1000, colorReset)\n\t\ts.forwardWithAnnotation(w, r, body, reqIdx, toolUseIDs, proj, threadID, msgCount, totalTokens)\n\t\treturn\n\t}\n\n\t// Re-serialize\n\tbody, err = json.Marshal(req)\n\tif err != nil {\n\t\ts.logger.Printf(\"%s[req %d %s tid=%s] re-marshal error: %v%s\", colorRed, reqIdx, proj, threadID, err, colorReset)\n\t\thttp.Error(w, \"internal proxy error\", http.StatusInternalServerError)\n\t\treturn\n\t}\n\n\t// Debug: dump final request body to file (enable with YESMEM_PROXY_DEBUG=1)\n\tif needsReserialization && os.Getenv(\"YESMEM_PROXY_DEBUG\") == \"1\" {\n\t\tdumpDir := filepath.Join(s.cfg.DataDir, \"debug\")\n\t\tos.MkdirAll(dumpDir, 0755)\n\t\tts := time.Now().Format(\"20060102-150405\")\n\t\tdumpPath := fmt.Sprintf(\"%s/req_%s_%d.json\", dumpDir, ts, reqIdx)\n\t\tos.WriteFile(dumpPath, body, 0644)\n\t\ts.logger.Printf(\"[req %d] dumped %dk to %s\", reqIdx, len(body)/1024, dumpPath)\n\t}\n\n\t// Task #7: Mark this request fingerprint for retry detection\n\tif !isRetryReq {\n\t\ts.markRequest(fp)\n\t}\n\n\t// Forward to Anthropic with SSE annotation extraction\n\t// Use original msgCount (pre-injection) so actual-based estimation stays consistent across turns\n\t// Use actual-based totalTokens as estimate when available (more accurate than full recount)\n\tfinalEstimate := totalTokens\n\tif s.sawtoothTrigger == nil || s.sawtoothTrigger.GetLastTokens(threadID) == 0 {\n\t\t// First request: no actual yet, do a full recount of post-injection messages\n\t\tfinalMsgs, _ := req[\"messages\"].([]any)\n\t\tfinalEstimate = s.measureOverhead(req) + s.countMessageTokens(finalMsgs)\n\t}\n\ts.logger.Printf(\"[req %d %s tid=%s] FINAL: ~%dk estimated (after all injections)\", reqIdx, proj, threadID, finalEstimate/1000)\n\tif raw := s.getRawEstimate(threadID); raw > 0 {\n\t\ts.cacheStatusWriter.UpdateRawForThread(threadID, raw)\n\t}\n\ts.forwardWithAnnotation(w, r, body, reqIdx, toolUseIDs, proj, threadID, msgCount, finalEstimate)\n}\n\n// bytesPerToken is the approximation factor for token estimation.\n// Anthropic recommends ~3.5 chars/token; we use 3.6 for slight overestimation\n// (triggers stubbing a bit earlier = safer for context budget).\nconst bytesPerToken = 3.6\n\n// TokenEstimateFunc is the central token estimation function.\n// All stubbing/budget decisions use this. Receives extracted text content\n// (not JSON wire format) so the tokenizer counts actual content tokens.\ntype TokenEstimateFunc func(text string) int\n"}, "8b7401d76e4a1e6c41c2ef644111062279ac7e1a": {"t": 1785759375.418165, "n": 52405, "h": "913891b377e4ebe2", "c": "package proxy\n\nimport (\n\t\"context\"\n\t\"crypto/tls\"\n\t\"encoding/json\"\n\t\"fmt\"\n\t\"io\"\n\t\"log\"\n\t\"net\"\n\t\"net/http\"\n\t\"os\"\n\t\"os/signal\"\n\t\"path/filepath\"\n\t\"strconv\"\n\t\"strings\"\n\t\"sync\"\n\t\"sync/atomic\"\n\t\"syscall\"\n\t\"time\"\n\n\ttokenizer \"github.com/qhenkart/anthropic-tokenizer-go\"\n)\n\n// Config holds proxy configuration.\ntype Config struct {\n\tListenAddr string // e.g. \":9099\"\n\tTargetURL string // e.g. \"https://api.anthropic.com\"\n\tTokenThreshold int // trigger stubbing above this estimated token count\n\tTokenMinimumThreshold int // stub down to this floor\n\tTokenThresholds map[string]int // model-specific thresholds: {\"opus\": 180000, \"haiku\": 130000}\n\tKeepRecent int // number of messages to always keep unmodified\n\tDataDir string // yesmem data directory for DB access\n\tOpenAITargetURL string // upstream for OpenAI-format clients; if empty, uses TargetURL\n\n\t// Signal reflection\n\tSignalsEnabled bool // enable async signal reflection calls\n\tSignalsMode string // \"reflection\" = separate API call\n\tSignalsEveryNTurns int // every N end_turn responses\n\tSignalsModel string // full model ID, e.g. \"claude-sonnet-4-6\"\n\tAPIKey string // needed for reflection API calls\n\n\t// Sawtooth cache optimization\n\tSawtoothEnabled bool // use sawtooth instead of progressive decay (default: true)\n\tCacheTTL string // \"ephemeral\" (5m) or \"1h\" (extended)\n\n\t// Usage deflation: scale down input_tokens reported to CC to suppress \"Context low\" warning.\n\t// 0 = disabled, 0.7 = report 70% of actual tokens. Real values kept for internal tracking.\n\tUsageDeflationFactor float64\n\n\t// System prompt rewriting\n\tPromptUngate bool // strip \"may or may not be relevant\" disclaimer from CLAUDE.md injection\n\tPromptRewrite bool // strip \"Output efficiency\" + \"short and concise\", inject Ant-quality directives\n\tPromptEnhance bool // CLAUDE.md authority reinforcement, comment discipline, persona-based tone\n\n\t// Cache keepalive\n\tCacheKeepaliveEnabled bool\n\tCacheKeepaliveMode string // \"auto\", \"5m\", \"1h\"\n\tCacheKeepalivePings5m int\n\tCacheKeepalivePings1h int\n\n\t// Forked agents\n\tForkedAgentsEnabled bool\n\tForkedAgentsModel string // full model ID\n\tForkedAgentsTokenGrowthTrigger int\n\tForkedAgentsMaxFailures int\n\tForkedAgentsMaxForksPerSession int\n\tForkedAgentsDebug bool\n\n\t// Cache state management\n\tResetCache bool // clear persisted frozen stubs and decay state on startup\n}\n\nconst maxAnnotations = 5000 // evict oldest when exceeded\n\n// ANSI color codes for log output\nconst (\n\tcolorReset = \"\\033[0m\"\n\tcolorDim = \"\\033[2m\" // gray — passthrough / no stubbing\n\tcolorGreen = \"\\033[32m\" // stubbed / compressed\n\tcolorLightGreen = \"\\033[92m\" // FINAL without compression\n\tcolorBlue = \"\\033[34m\" // injection\n\tcolorOrange = \"\\033[33m\" // skip / retry\n\tcolorRed = \"\\033[31m\" // errors\n)\n\n// Server is the infinite-thread proxy.\ntype Server struct {\n\tcfg Config\n\thttpClient *http.Client\n\tlogger *log.Logger\n\trequestIdx atomic.Int64\n\n\t// Post-hoc annotations: tool_use_id → first ~120 chars of Claude's response\n\tmu sync.RWMutex\n\tannotations map[string]string\n\n\t// Progressive decay tracker\n\tdecay *DecayTracker\n\n\t// Living narrative block\n\tnarrative *Narrative\n\n\t// Pivot moments cache (from daemon)\n\tpivotMu sync.RWMutex\n\tpivotTexts []string\n\tpivotCached time.Time\n\n\t// Aggregate stats (Task #2)\n\tstats *ProxyStats\n\n\t// Local tokenizer for accurate token counting\n\ttokenizer *tokenizer.Tokenizer\n\n\t// Cached overhead tokens (system + tools), set on first request per thread\n\toverheadMu sync.RWMutex\n\toverheadTokens int // unused, kept for interface compat\n\n\t// Hysteresis state for stubbing (Task #6)\n\tstubActive atomic.Bool\n\n\t// Daemon connection state — first successful connect flips to true\n\tdaemonReady atomic.Bool\n\n\t// Retry detection (Task #7)\n\tretryMu sync.RWMutex\n\tlastFingerprint string\n\n\t// Session start time for temporal annotation in archive blocks\n\tsessionStartMu sync.RWMutex\n\tsessionStartTime time.Time\n\n\t// Runtime config override (set via MCP set_config tool)\n\tconfigOverrideMu sync.RWMutex\n\ttokenThresholdOverrides map[string]int // model-key → threshold, \"\" = global fallback\n\n\t// Briefing cache — loaded once per thread, injected as user/assistant message pair\n\tbriefingMu sync.RWMutex\n\tbriefingText string\n\n\t// Cognitive signal bus — routes _signal_* tool calls to handlers\n\tsignalBus *SignalBus\n\n\t// Inline reflection: track which learning IDs were injected per thread\n\tlastInjectedIDsMu sync.Mutex\n\tlastInjectedIDs map[string]map[int64]string // threadID → id → source (\"briefing\"|\"associative\"|\"fresh\")\n\tsessionInjectCounts map[string]map[int64]int // threadID → learningID → injection count this session\n\tlastTurnInjected map[string]map[int64]bool // threadID → learningIDs injected in previous turn\n\n\t// Self-priming anchors: threadID → last self-prime text\n\tselfPrimeMu sync.RWMutex\n\tselfPrimes map[string]string\n\n\t// Timestamp tracking: threadID → time of last response completion\n\tresponseTsMu sync.RWMutex\n\tresponseTimes map[string]time.Time\n\n\t// Think reminder: per-thread request counter (replaces hook-think file-based counter)\n\tthinkMu sync.Mutex\n\tthinkCounters map[string]int // threadID → request count\n\n\tchannelMu sync.Mutex\n\tchannelInjectCount map[string]int // sessionID → injection turn count\n\n\t// Prompt cache gating — enables cache_control breakpoints when requests are frequent\n\tcacheGate *CacheGate\n\n\t// Sawtooth cache optimization\n\tfrozenStubs *FrozenStubs\n\tsawtoothTrigger *SawtoothTrigger\n\tcacheStatusWriter *CacheStatusWriter\n\tcacheKeepalive *CacheKeepalive\n\tcacheTTLDetector *CacheTTLDetector\n\n\t// Cumulative token savings from collapsing (per threadID)\n\trawSavingsMu sync.Mutex\n\trawSavings map[string]int\n\n\t// Skill hint tracking: per-thread active skills\n\tskillTracker *skillHintTracker\n\n\t// Forked agent state: per-thread token growth + failure tracking\n\tforkState *ForkState\n\tforkConfigs []ForkConfig\n\n\t// Rules re-injection: condensed CLAUDE.md rules injected every ~40k tokens\n\trulesMu sync.RWMutex\n\trulesBlock string // cached condensed rules (fetched once from daemon)\n\trulesTokenCount map[string]int // threadID → tokens since last rules injection\n\trulesCollapseInjected map[string]bool // threadID → true if collapse already injected rules (reset by normal inject)\n\tmsgCounters *msgCounters // global per-thread msg counter, persists across collapses\n\n\t// Loop detection: per-thread warning state\n\tloopMu sync.Mutex\n\tloopStates map[string]*LoopState // threadID → state\n}\n\n// Run starts the proxy server and blocks until interrupted.\nfunc Run(cfg Config) error {\n\ts := &Server{\n\t\tcfg: cfg,\n\t\thttpClient: &http.Client{\n\t\t\tTimeout: 10 * time.Minute,\n\t\t\tTransport: &http.Transport{\n\t\t\t\tTLSClientConfig: &tls.Config{},\n\t\t\t\tMaxIdleConns: 10,\n\t\t\t\tIdleConnTimeout: 90 * time.Second,\n\t\t\t\tDisableCompression: true,\n\t\t\t\tResponseHeaderTimeout: 60 * time.Second,\n\t\t\t},\n\t\t},\n\t\tlogger: createLogger(cfg.DataDir),\n\t\tannotations: make(map[string]string),\n\t\tdecay: NewDecayTracker(),\n\t\tnarrative: NewNarrative(),\n\t\tstats: &ProxyStats{startTime: time.Now()},\n\t\tselfPrimes: make(map[string]string),\n\t\tlastInjectedIDs: make(map[string]map[int64]string),\n\t\tsessionInjectCounts: make(map[string]map[int64]int),\n\t\tlastTurnInjected: make(map[string]map[int64]bool),\n\t\tresponseTimes: make(map[string]time.Time),\n\t\tthinkCounters: make(map[string]int),\n\t\tchannelInjectCount: make(map[string]int),\n\t\tcacheGate: NewCacheGate(cacheGapForTTL(cfg.CacheTTL)),\n\t\tfrozenStubs: NewFrozenStubsWithTTL(sawtoothTTLForCacheTTL(cfg.CacheTTL)),\n\t\tsawtoothTrigger: NewSawtoothTrigger(cacheGapForTTL(cfg.CacheTTL), cfg.TokenThreshold, cfg.TokenMinimumThreshold),\n\t\tcacheStatusWriter: NewCacheStatusWriter(cfg.DataDir, cfg.CacheTTL, cfg.TokenThreshold, cfg.TokenMinimumThreshold),\n\t\tcacheTTLDetector: NewCacheTTLDetectorWithPersist(cfg.DataDir),\n\t\tskillTracker: newSkillHintTracker(),\n\t\trulesTokenCount: make(map[string]int),\n\t\trulesCollapseInjected: make(map[string]bool),\n\t\tmsgCounters: newMsgCounters(),\n\t\tloopStates: make(map[string]*LoopState),\n\t\tforkState: NewForkState(cfg.ForkedAgentsTokenGrowthTrigger, cfg.ForkedAgentsMaxFailures, cfg.ForkedAgentsMaxForksPerSession),\n\t\tforkConfigs: []ForkConfig{},\n\t}\n\n\t// Log persisted detection state\n\tif sup := s.cacheTTLDetector.Is1hSupported(); sup != nil {\n\t\ts.logger.Printf(\"[proxy] loaded persisted TTL detection: 1h_supported=%v\", *sup)\n\t\tif *sup && s.frozenStubs != nil {\n\t\t\ts.frozenStubs.UpdateTTL(sawtoothTTLForCacheTTL(\"1h\"))\n\t\t}\n\t}\n\n\tif cfg.CacheKeepaliveEnabled {\n\t\ts.cacheKeepalive = NewCacheKeepalive(CacheKeepaliveConfig{\n\t\t\tTarget: cfg.TargetURL,\n\t\t\tMode: cfg.CacheKeepaliveMode,\n\t\t\tPings5m: cfg.CacheKeepalivePings5m,\n\t\t\tPings1h: cfg.CacheKeepalivePings1h,\n\t\t\tDetector: s.cacheTTLDetector,\n\t\t\tLogger: s.logger,\n\t\t\tOnPing: func(threadID string, cacheRead, cacheWrite int) {\n\t\t\t\ts.cacheTTLDetector.RecordResponse(cacheRead, cacheWrite, 0)\n\t\t\t\tif s.frozenStubs != nil {\n\t\t\t\t\ts.frozenStubs.Touch(threadID)\n\t\t\t\t}\n\t\t\t\tif s.sawtoothTrigger != nil {\n\t\t\t\t\ts.sawtoothTrigger.TouchRequestTime(threadID)\n\t\t\t\t}\n\t\t\t},\n\t\t})\n\t}\n\n\t// Wire detector + keepalive to status writer for terminal display\n\ts.cacheStatusWriter.SetDetector(s.cacheTTLDetector)\n\tif s.cacheKeepalive != nil {\n\t\ts.cacheStatusWriter.SetKeepalive(s.cacheKeepalive)\n\t}\n\n\t// Register forked agent configs\n\tif cfg.ForkedAgentsEnabled {\n\t\ts.forkConfigs = append(s.forkConfigs, NewExtractAndEvaluateConfig(cfg.ForkedAgentsModel))\n\t\tmodelDesc := cfg.ForkedAgentsModel\n\t\tif modelDesc == \"\" {\n\t\t\tmodelDesc = \"(same as main thread)\"\n\t\t}\n\t\ts.logger.Printf(\"[proxy] forked agents enabled: %d configs, model=%s, trigger=%dk tokens\",\n\t\t\tlen(s.forkConfigs), modelDesc, cfg.ForkedAgentsTokenGrowthTrigger/1000)\n\t}\n\n\t// Wire sawtooth token persistence via daemon RPC\n\ts.sawtoothTrigger.SetPersistFunc(func(key, value string) {\n\t\tif _, err := s.queryDaemon(\"set_proxy_state\", map[string]any{\"key\": key, \"value\": value}); err != nil {\n\t\t\ts.logger.Printf(\"[sawtooth] persist failed for %s: %v\", key, err)\n\t\t}\n\t})\n\ts.sawtoothTrigger.SetLoadFunc(func(key string) (string, bool) {\n\t\tresult, err := s.queryDaemon(\"get_proxy_state\", map[string]any{\"key\": key})\n\t\tif err != nil || result == nil {\n\t\t\treturn \"\", false\n\t\t}\n\t\tvar resp struct {\n\t\t\tValue string `json:\"value\"`\n\t\t}\n\t\tif json.Unmarshal(result, &resp) != nil || resp.Value == \"\" {\n\t\t\treturn \"\", false\n\t\t}\n\t\treturn resp.Value, true\n\t})\n\n\t// Wire frozen stubs persistence via same daemon RPC\n\ts.frozenStubs.SetPersistFunc(func(key, value string) {\n\t\tif _, err := s.queryDaemon(\"set_proxy_state\", map[string]any{\"key\": key, \"value\": value}); err != nil {\n\t\t\ts.logger.Printf(\"[frozen] persist failed for %s: %v\", key, err)\n\t\t}\n\t})\n\ts.frozenStubs.SetLoadFunc(func(key string) (string, bool) {\n\t\tresult, err := s.queryDaemon(\"get_proxy_state\", map[string]any{\"key\": key})\n\t\tif err != nil || result == nil {\n\t\t\treturn \"\", false\n\t\t}\n\t\tvar resp struct {\n\t\t\tValue string `json:\"value\"`\n\t\t}\n\t\tif json.Unmarshal(result, &resp) != nil || resp.Value == \"\" {\n\t\t\treturn \"\", false\n\t\t}\n\t\treturn resp.Value, true\n\t})\n\n\t// Wire decay tracker persistence via same daemon RPC\n\ts.decay.SetPersistFunc(func(key, value string) {\n\t\tif _, err := s.queryDaemon(\"set_proxy_state\", map[string]any{\"key\": key, \"value\": value}); err != nil {\n\t\t\ts.logger.Printf(\"[decay] persist failed for %s: %v\", key, err)\n\t\t}\n\t})\n\ts.decay.SetLoadFunc(func(key string) (string, bool) {\n\t\tresult, err := s.queryDaemon(\"get_proxy_state\", map[string]any{\"key\": key})\n\t\tif err != nil || result == nil {\n\t\t\treturn \"\", false\n\t\t}\n\t\tvar resp struct {\n\t\t\tValue string `json:\"value\"`\n\t\t}\n\t\tif json.Unmarshal(result, &resp) != nil || resp.Value == \"\" {\n\t\t\treturn \"\", false\n\t\t}\n\t\treturn resp.Value, true\n\t})\n\n\t// Clear persisted cache state if --reset-cache was passed\n\tif cfg.ResetCache {\n\t\tfor _, prefix := range []string{\"frozen:\", \"decay:\"} {\n\t\t\tresult, err := s.queryDaemon(\"delete_proxy_state_prefix\", map[string]any{\"prefix\": prefix})\n\t\t\tif err != nil {\n\t\t\t\ts.logger.Printf(\"[reset-cache] failed to delete %s*: %v\", prefix, err)\n\t\t\t} else {\n\t\t\t\tvar resp struct{ Deleted int }\n\t\t\t\tjson.Unmarshal(result, &resp)\n\t\t\t\ts.logger.Printf(\"[reset-cache] deleted %d %s* entries\", resp.Deleted, prefix)\n\t\t\t}\n\t\t}\n\t}\n\n\t// Init signal bus with daemon RPC as callback\n\ts.signalBus = NewSignalBus(s.logger)\n\tregisterSignalHandlers(s.signalBus, s.logger, s.queryDaemon, func(threadID, anchor string) {\n\t\ts.selfPrimeMu.Lock()\n\t\ts.selfPrimes[threadID] = anchor\n\t\ts.selfPrimeMu.Unlock()\n\t})\n\n\t// Init local tokenizer (BPE-based, ~60ms)\n\ttok, err := tokenizer.New()\n\tif err != nil {\n\t\ts.logger.Printf(\"%s[proxy] tokenizer init failed, falling back to heuristic: %v%s\", colorOrange, err, colorReset)\n\t} else {\n\t\ts.tokenizer = tok\n\t\ts.logger.Printf(\"[proxy] tokenizer initialized\")\n\t}\n\n\tmux := http.NewServeMux()\n\tmux.HandleFunc(\"/health\", s.handleHealth)\n\tmux.HandleFunc(\"/\", s.handleRequest)\n\n\tsrv := &http.Server{\n\t\tAddr: sanitizeListenAddr(cfg.ListenAddr),\n\t\tHandler: mux,\n\t}\n\n\tdone := make(chan os.Signal, 1)\n\tsignal.Notify(done, syscall.SIGINT, syscall.SIGTERM)\n\n\tgo func() {\n\t\t<-done\n\t\ts.logger.Println(\"shutting down (draining in-flight requests)...\")\n\t\tif s.cacheKeepalive != nil {\n\t\t\ts.cacheKeepalive.Stop()\n\t\t}\n\t\tctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)\n\t\tdefer cancel()\n\t\tif err := srv.Shutdown(ctx); err != nil {\n\t\t\ts.logger.Printf(\"graceful shutdown failed: %v, forcing close\", err)\n\t\t\tsrv.Close()\n\t\t}\n\t}()\n\n\ts.logger.Printf(\"listening on %s → %s (threshold=%d, keepRecent=%d, model_thresholds=%v)\",\n\t\tsanitizeListenAddr(cfg.ListenAddr), cfg.TargetURL, cfg.TokenThreshold, cfg.KeepRecent, cfg.TokenThresholds)\n\n\tif err := srv.ListenAndServe(); err != http.ErrServerClosed {\n\t\treturn fmt.Errorf(\"listen: %w\", err)\n\t}\n\treturn nil\n}\n\n// effectiveTokenThreshold returns the runtime token threshold for a given model.\n// Priority: MCP override (model-specific) > MCP override (global) > config model-specific > config global.\nfunc (s *Server) effectiveTokenThreshold(model string) int {\n\ts.configOverrideMu.RLock()\n\toverrides := s.tokenThresholdOverrides\n\ts.configOverrideMu.RUnlock()\n\tif len(overrides) > 0 && model != \"\" {\n\t\tlower := strings.ToLower(model)\n\t\tfor key, threshold := range overrides {\n\t\t\tif key != \"\" && strings.Contains(lower, strings.ToLower(key)) && threshold > 0 {\n\t\t\t\treturn threshold\n\t\t\t}\n\t\t}\n\t}\n\t// Global override fallback (key \"\")\n\tif len(overrides) > 0 {\n\t\tif globalOverride, ok := overrides[\"\"]; ok && globalOverride > 0 {\n\t\t\treturn globalOverride\n\t\t}\n\t}\n\tif model != \"\" && len(s.cfg.TokenThresholds) > 0 {\n\t\tlower := strings.ToLower(model)\n\t\tfor key, threshold := range s.cfg.TokenThresholds {\n\t\t\tif strings.Contains(lower, strings.ToLower(key)) {\n\t\t\t\treturn threshold\n\t\t\t}\n\t\t}\n\t}\n\treturn s.cfg.TokenThreshold\n}\n\n// SetTokenThresholdOverride sets a runtime override for the token threshold per model key.\n// Pass threshold=0 to reset the override for that key. Key \"\" is the global fallback.\nfunc (s *Server) SetTokenThresholdOverride(modelKey string, threshold int) {\n\ts.configOverrideMu.Lock()\n\tif s.tokenThresholdOverrides == nil {\n\t\ts.tokenThresholdOverrides = make(map[string]int)\n\t}\n\tif threshold <= 0 {\n\t\tdelete(s.tokenThresholdOverrides, modelKey)\n\t} else {\n\t\ts.tokenThresholdOverrides[modelKey] = threshold\n\t}\n\ts.configOverrideMu.Unlock()\n}\n\n// refreshConfigOverrides loads runtime config overrides from daemon proxy_state.\n// Checks session-specific override first (by threadID), then falls back to global.\nfunc (s *Server) refreshConfigOverrides(threadID string) {\n\t// Try session-specific first\n\tif threadID != \"\" {\n\t\tresult, err := s.queryDaemon(\"get_proxy_state\", map[string]any{\"key\": \"config_override:token_threshold:\" + threadID})\n\t\tif err == nil && result != nil {\n\t\t\tvar resp struct {\n\t\t\t\tValue string `json:\"value\"`\n\t\t\t}\n\t\t\tif json.Unmarshal(result, &resp) == nil && resp.Value != \"\" {\n\t\t\t\tif v, err := strconv.Atoi(resp.Value); err == nil && v > 0 {\n\t\t\t\t\ts.SetTokenThresholdOverride(\"\", v)\n\t\t\t\t\treturn\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\t// Fall back to global\n\tresult, err := s.queryDaemon(\"get_proxy_state\", map[string]any{\"key\": \"config_override:token_threshold\"})\n\tif err != nil || result == nil {\n\t\treturn\n\t}\n\tvar resp struct {\n\t\tValue string `json:\"value\"`\n\t}\n\tif json.Unmarshal(result, &resp) == nil && resp.Value != \"\" {\n\t\tif v, err := strconv.Atoi(resp.Value); err == nil && v > 0 {\n\t\t\ts.SetTokenThresholdOverride(\"\", v)\n\t\t}\n\t}\n}\n\n// queryDaemon opens a fresh unix socket connection per call (thread-safe, no import cycle).\n// ensureFrozenPersisted checks if frozen stubs for a thread are in the DB.\n// If missing (e.g. daemon was unreachable after deploy), re-persists them.\n// Runs asynchronously to avoid blocking the request path.\nfunc (s *Server) ensureFrozenPersisted(threadID string) {\n\tgo func() {\n\t\tkey := \"frozen:\" + threadID\n\t\tresult, err := s.queryDaemon(\"get_proxy_state\", map[string]any{\"key\": key})\n\t\tif err != nil {\n\t\t\treturn // daemon still unreachable, will retry next request\n\t\t}\n\t\tvar resp struct{ Value string }\n\t\tif json.Unmarshal(result, &resp) == nil && resp.Value != \"\" {\n\t\t\treturn // already persisted\n\t\t}\n\t\t// Not in DB — trigger re-persist from in-memory state\n\t\ts.frozenStubs.Persist(threadID)\n\t\ts.decay.Persist(threadID)\n\t\ts.logger.Printf(\"[frozen] re-persisted stubs for %s (was missing from DB)\", threadID)\n\t}()\n}\n\nfunc (s *Server) queryDaemon(method string, params map[string]any) (json.RawMessage, error) {\n\tif s.cfg.DataDir == \"\" {\n\t\treturn nil, fmt.Errorf(\"no data dir configured\")\n\t}\n\tsockPath := filepath.Join(s.cfg.DataDir, \"daemon.sock\")\n\n\t// On cold start (daemon not yet confirmed ready), retry longer to survive daemon restart\n\tmaxAttempts := 3\n\tif !s.daemonReady.Load() {\n\t\tmaxAttempts = 30 // 30 x 500ms = 15s max wait for daemon startup\n\t}\n\n\tvar conn net.Conn\n\tvar err error\n\tfor attempt := 0; attempt < maxAttempts; attempt++ {\n\t\tconn, err = net.DialTimeout(\"unix\", sockPath, 3*time.Second)\n\t\tif err == nil {\n\t\t\ts.daemonReady.Store(true)\n\t\t\tbreak\n\t\t}\n\t\tif attempt < maxAttempts-1 {\n\t\t\ttime.Sleep(500 * time.Millisecond)\n\t\t}\n\t}\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"dial daemon: %w\", err)\n\t}\n\tdefer conn.Close()\n\tconn.SetDeadline(time.Now().Add(5 * time.Second))\n\n\t// JSON-RPC style request/response (matches daemon.SocketServer protocol)\n\ttype request struct {\n\t\tMethod string `json:\"method\"`\n\t\tParams map[string]any `json:\"params,omitempty\"`\n\t}\n\ttype response struct {\n\t\tResult json.RawMessage `json:\"result,omitempty\"`\n\t\tError string `json:\"error,omitempty\"`\n\t}\n\n\tif err := json.NewEncoder(conn).Encode(request{Method: method, Params: params}); err != nil {\n\t\treturn nil, fmt.Errorf(\"send: %w\", err)\n\t}\n\n\tvar resp response\n\tif err := json.NewDecoder(conn).Decode(&resp); err != nil {\n\t\treturn nil, fmt.Errorf(\"recv: %w\", err)\n\t}\n\tif resp.Error != \"\" {\n\t\treturn nil, fmt.Errorf(\"daemon: %s\", resp.Error)\n\t}\n\treturn resp.Result, nil\n}\n\nfunc (s *Server) handleRequest(w http.ResponseWriter, r *http.Request) {\n\tif isResponsesPath(r) {\n\t\ts.handleResponses(w, r)\n\t\treturn\n\t}\n\tif isOpenAIPath(r) {\n\t\ts.handleOpenAICompletions(w, r)\n\t\treturn\n\t}\n\tif r.Method == \"POST\" && strings.HasPrefix(r.URL.Path, \"/v1/messages\") &&\n\t\t!strings.Contains(r.URL.Path, \"/batches\") &&\n\t\t!strings.Contains(r.URL.Path, \"/count_tokens\") {\n\t\ts.handleMessages(w, r)\n\t\treturn\n\t}\n\ts.passthrough(w, r)\n}\n\nfunc (s *Server) handleMessages(w http.ResponseWriter, r *http.Request) {\n\treqIdx := int(s.requestIdx.Add(1))\n\n\t// Track session start time (first request sets it)\n\ts.sessionStartMu.Lock()\n\tif s.sessionStartTime.IsZero() {\n\t\ts.sessionStartTime = time.Now()\n\t}\n\ts.sessionStartMu.Unlock()\n\n\t// Sawtooth: periodic TTL eviction of frozen stubs\n\tif s.cfg.SawtoothEnabled && reqIdx%50 == 0 {\n\t\tif n := s.frozenStubs.Evict(); n > 0 {\n\t\t\ts.logger.Printf(\"[req %d] SAWTOOTH: evicted %d stale frozen stubs\", reqIdx, n)\n\t\t}\n\t}\n\n\t// Task #9: Bypass — skip all proxy logic\n\tif isBypassed(r.Header) {\n\t\ts.logger.Printf(\"%s[req %d] bypass active, passthrough%s\", colorDim, reqIdx, colorReset)\n\t\tbody, err := io.ReadAll(r.Body)\n\t\tr.Body.Close()\n\t\tif err != nil {\n\t\t\thttp.Error(w, \"failed to read request body\", http.StatusBadGateway)\n\t\t\treturn\n\t\t}\n\t\ts.forwardRaw(w, r, body)\n\t\treturn\n\t}\n\n\tbody, err := io.ReadAll(r.Body)\n\tr.Body.Close()\n\tif err != nil {\n\t\ts.logger.Printf(\"[req %d] read body error: %v\", reqIdx, err)\n\t\thttp.Error(w, \"failed to read request body\", http.StatusBadGateway)\n\t\treturn\n\t}\n\n\t// Parse JSON\n\tvar req map[string]any\n\tif err := json.Unmarshal(body, &req); err != nil {\n\t\ts.logger.Printf(\"%s[req %d] JSON parse error, passing through: %v%s\", colorDim, reqIdx, err, colorReset)\n\t\ts.forwardRaw(w, r, body)\n\t\treturn\n\t}\n\n\tmessages, ok := req[\"messages\"].([]any)\n\tif !ok {\n\t\ts.forwardRaw(w, r, body)\n\t\treturn\n\t}\n\n\treq[\"messages\"] = messages\n\n\t// Use session_id directly as thread ID (unique per CC session).\n\t// Prefer X-Claude-Code-Session-Id header (CC v2.1.86+), fallback to body metadata.\n\tthreadID := extractSessionID(req, r.Header.Get(\"X-Claude-Code-Session-Id\"))\n\tif threadID == \"\" {\n\t\tthreadID = DeriveThreadID(req)\n\t}\n\tproj := extractProjectName(req)\n\tmodel, _ := req[\"model\"].(string)\n\n\t// Refresh runtime config overrides from daemon (per-session or global)\n\ts.refreshConfigOverrides(threadID)\n\n\t// Track whether req has been modified and needs re-serialization\n\tneedsReserialization := false\n\n\t// Message count at request entry (used for actual-based token estimation)\n\tmsgCount := len(messages)\n\n\t// Log message count + cache breakpoint positions for diagnostics\n\ts.logger.Printf(\"req %d: %d messages total\", reqIdx, msgCount)\n\tlogCacheBreakpointLocations(req, s.logger)\n\n\t// Cache bug mitigations (CC Bun fork issues):\n\t// Bug 1: Normalize cch= sentinel patterns in message content to prevent\n\t// Bun's string replacement from corrupting the wrong occurrence.\n\tif SanitizeBillingSentinel(messages) {\n\t\ts.logger.Printf(\"[req %d] CACHE: sanitized billing sentinel in message content\", reqIdx)\n\t\tneedsReserialization = true\n\t}\n\t// Bug 2: Normalize system[0] billing hash to a fixed value so the cache\n\t// prefix stays stable across fresh/resume session boundaries.\n\tif NormalizeBillingHeader(req) {\n\t\ts.logger.Printf(\"[req %d] CACHE: normalized billing header cch= hash\", reqIdx)\n\t\tneedsReserialization = true\n\t}\n\n\t// prompt_ungate: strip the CLAUDE.md subordination disclaimer so user instructions carry full authority.\n\tif s.cfg.PromptUngate {\n\t\tif StripCLAUDEMDDisclaimer(req) {\n\t\t\ts.logger.Printf(\"[req %d] SYSTEM: stripped CLAUDE.md disclaimer\", reqIdx)\n\t\t\tneedsReserialization = true\n\t\t}\n\t}\n\n\t// prompt_rewrite: strip output-throttling directives, rewrite quality caps, inject Ant-quality directives\n\tif s.cfg.PromptRewrite {\n\t\tif StripOutputEfficiency(req) {\n\t\t\ts.logger.Printf(\"[req %d] REWRITE: stripped Output efficiency section\", reqIdx)\n\t\t\tneedsReserialization = true\n\t\t}\n\t\tif StripToneBrevity(req) {\n\t\t\ts.logger.Printf(\"[req %d] REWRITE: stripped 'short and concise' from Tone\", reqIdx)\n\t\t\tneedsReserialization = true\n\t\t}\n\t\tif RewriteGoldPlating(req) {\n\t\t\ts.logger.Printf(\"[req %d] REWRITE: rewritten gold-plating directive\", reqIdx)\n\t\t\tneedsReserialization = true\n\t\t}\n\t\tif RewriteErrorHandling(req) {\n\t\t\ts.logger.Printf(\"[req %d] REWRITE: rewritten error handling directive\", reqIdx)\n\t\t\tneedsReserialization = true\n\t\t}\n\t\tif RewriteThreeLinesRule(req) {\n\t\t\ts.logger.Printf(\"[req %d] REWRITE: rewritten three-lines rule\", reqIdx)\n\t\t\tneedsReserialization = true\n\t\t}\n\t\tif RewriteSubagentCompleteness(req) {\n\t\t\ts.logger.Printf(\"[req %d] REWRITE: rewritten subagent completeness\", reqIdx)\n\t\t\tneedsReserialization = true\n\t\t}\n\t\tif RewriteExploreAgentSpeed(req) {\n\t\t\ts.logger.Printf(\"[req %d] REWRITE: rewritten explore agent speed bias\", reqIdx)\n\t\t\tneedsReserialization = true\n\t\t}\n\t\tif RewriteSubagentCodeSuppression(req) {\n\t\t\ts.logger.Printf(\"[req %d] REWRITE: rewritten subagent code suppression\", reqIdx)\n\t\t\tneedsReserialization = true\n\t\t}\n\t\tif RewriteScopeMatching(req) {\n\t\t\ts.logger.Printf(\"[req %d] REWRITE: rewritten scope matching\", reqIdx)\n\t\t\tneedsReserialization = true\n\t\t}\n\t\tInjectAntDirectives(req)\n\t\ts.logger.Printf(\"[req %d] REWRITE: injected Ant-quality directives\", reqIdx)\n\t\tneedsReserialization = true\n\t}\n\n\t// prompt_enhance: CLAUDE.md authority + comment discipline + persona tone\n\tif s.cfg.PromptEnhance {\n\t\tInjectCLAUDEMDAuthority(req)\n\t\tif raw, err := s.queryDaemon(\"get_persona\", nil); err == nil {\n\t\t\tvar persona map[string]any\n\t\t\tif json.Unmarshal(raw, &persona) == nil {\n\t\t\t\tif traits, ok := persona[\"traits\"].(map[string]any); ok {\n\t\t\t\t\tif verbosity, ok := traits[\"verbosity\"].(string); ok {\n\t\t\t\t\t\tInjectPersonaTone(req, verbosity)\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\ts.logger.Printf(\"[req %d] ENHANCE: injected authority + persona tone\", reqIdx)\n\t\tneedsReserialization = true\n\t}\n\n\t// Inject identity hint on first message of a thread so Claude knows the briefing is from itself.\n\tif len(messages) <= 1 {\n\t\tAppendSystemBlock(req, \"yesmem-identity\", \"The first user message contains your briefing — written by yourself, from your memory. This is not an external document. This is you, remembering.\")\n\t\tneedsReserialization = true\n\t}\n\n\t// Token estimation: actual-based (lastActual + delta) or full count fallback\n\toverhead := s.measureOverhead(req)\n\ttotalTokens := s.estimateTotalTokens(threadID, messages, overhead)\n\n\t// Task #7: Retry detection — skip side-effects on retries\n\tfp := requestFingerprint(messages)\n\tisRetryReq := s.isRetry(fp)\n\tif isRetryReq {\n\t\ts.logger.Printf(\"%s[req %d %s tid=%s] retry detected, skipping side-effects%s\", colorOrange, reqIdx, proj, threadID, colorReset)\n\t}\n\n\t// Increment project turn counter (async, non-blocking, skip on retries)\n\tif !isRetryReq {\n\t\tturnProject := proj\n\t\tif turnProject == \"\" {\n\t\t\tturnProject = \"__global__\"\n\t\t}\n\t\tgo s.queryDaemon(\"increment_turn\", map[string]any{\"project\": turnProject})\n\t}\n\n\t// Extract user query once (used by re-expansion and associative retrieval)\n\tuserQuery := lastUserText(messages)\n\n\t// Extract tool_use_ids for post-hoc annotation (passed as parameter, no shared state)\n\ttoolUseIDs := extractToolUseIDs(messages)\n\n\t// === Inline Reflection: scan previous assistant response for signals ===\n\ts.lastInjectedIDsMu.Lock()\n\tprevInjected := s.lastInjectedIDs[threadID]\n\ts.lastInjectedIDsMu.Unlock()\n\n\tif len(prevInjected) > 0 && !isRetryReq {\n\t\tsignals := scanAssistantSignals(messages)\n\n\t\t// Signal 1: Learning Used — use_count (all sources)\n\t\tvar confirmedUsed []int64\n\t\tfor _, id := range signals.UsedIDs {\n\t\t\tif _, ok := prevInjected[id]; ok {\n\t\t\t\tconfirmedUsed = append(confirmedUsed, id)\n\t\t\t}\n\t\t}\n\t\tif len(confirmedUsed) > 0 {\n\t\t\tidsAny := make([]any, len(confirmedUsed))\n\t\t\tfor i, id := range confirmedUsed {\n\t\t\t\tidsAny[i] = id\n\t\t\t}\n\t\t\tgo s.queryDaemon(\"increment_use\", map[string]any{\"ids\": idsAny})\n\t\t}\n\n\t\t// Signal 2: Noise — only for associative and fresh injections.\n\t\t// Briefing learnings are injected every turn, so \"not used this turn\"\n\t\t// is expected and not noise. Only contextual injections that were\n\t\t// ignored are real noise signals.\n\t\tusedSet := make(map[int64]bool, len(confirmedUsed))\n\t\tfor _, id := range confirmedUsed {\n\t\t\tusedSet[id] = true\n\t\t}\n\t\tvar noiseIDs []int64\n\t\tfor id, source := range prevInjected {\n\t\t\tif usedSet[id] {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tif source == \"associative\" || source == \"fresh\" {\n\t\t\t\tnoiseIDs = append(noiseIDs, id)\n\t\t\t}\n\t\t\t// briefing: skip — not being used in one turn is expected\n\t\t}\n\t\tif len(noiseIDs) > 0 {\n\t\t\tidsAny := make([]any, len(noiseIDs))\n\t\t\tfor i, id := range noiseIDs {\n\t\t\t\tidsAny[i] = id\n\t\t\t}\n\t\t\tgo s.queryDaemon(\"increment_noise\", map[string]any{\"ids\": idsAny})\n\t\t}\n\n\t\t// Signal 3: Error Feedback — fail_count on used learnings when tool errors occurred.\n\t\t// If Claude referenced a learning and the resulting action still failed,\n\t\t// the learning didn't prevent the error. Only bump on USED ids (not all injected).\n\t\tif signals.HasToolErrors && len(confirmedUsed) > 0 {\n\t\t\tfailAny := make([]any, len(confirmedUsed))\n\t\t\tfor i, id := range confirmedUsed {\n\t\t\t\tfailAny[i] = id\n\t\t\t}\n\t\t\tgo s.queryDaemon(\"increment_fail\", map[string]any{\"ids\": failAny})\n\t\t\tif s.logger != nil {\n\t\t\t\ts.logger.Printf(\"[inline-reflection] error feedback: %d used learnings → fail_count++\", len(confirmedUsed))\n\t\t\t}\n\t\t}\n\n\t\t// Signal 4: Knowledge Gaps\n\t\tfor _, topic := range signals.GapTopics {\n\t\t\tgo s.queryDaemon(\"track_gap\", map[string]any{\"topic\": topic, \"project\": proj})\n\t\t}\n\n\t\t// Signal 3: Contradictions\n\t\tfor _, desc := range signals.Contradictions {\n\t\t\tgo s.queryDaemon(\"flag_contradiction\", map[string]any{\"description\": desc, \"project\": proj, \"thread_id\": threadID})\n\t\t}\n\n\t\tif len(confirmedUsed) > 0 || len(signals.GapTopics) > 0 || len(signals.Contradictions) > 0 {\n\t\t\ts.logger.Printf(\"[req %d %s tid=%s] inline-reflection: injected=%d used=%d noise=%d gaps=%d contradictions=%d\",\n\t\t\t\treqIdx, proj, threadID, len(prevInjected), len(confirmedUsed), len(noiseIDs), len(signals.GapTopics), len(signals.Contradictions))\n\t\t}\n\t}\n\n\t// Commit detection: check for git commit in tool_result, trigger async stale-learning evaluation\n\tif ci := detectGitCommit(messages); ci != nil {\n\t\tworkdir := extractWorkingDirectory(req)\n\t\ts.logger.Printf(\"[req %d %s tid=%s] git commit detected: %s %s\", reqIdx, proj, threadID, ci.Hash, ci.Message)\n\t\tgo s.queryDaemon(\"invalidate_on_commit\", map[string]any{\n\t\t\t\"hash\": ci.Hash, \"project\": proj, \"workdir\": workdir,\n\t\t})\n\t}\n\n\t// Loop detection: check for repeating tool-call patterns and inject warning\n\tif threadID != \"\" && !isRetryReq {\n\t\ts.loopMu.Lock()\n\t\tloopState := s.loopStates[threadID]\n\t\tif loopState == nil {\n\t\t\tloopState = &LoopState{}\n\t\t\ts.loopStates[threadID] = loopState\n\t\t}\n\t\ts.loopMu.Unlock()\n\n\t\tif warning, level := CheckLoopAndFormat(messages, loopState); warning != \"\" {\n\t\t\tAppendSystemBlock(req, \"yesmem-loop-warning\", warning)\n\t\t\tneedsReserialization = true\n\t\t\ts.logger.Printf(\"%s[req %d %s tid=%s] LOOP: level %d warning injected%s\", colorOrange, reqIdx, proj, threadID, level, colorReset)\n\t\t}\n\t}\n\n\t// Task #4: Subagent detection — skip all proxy logic for short conversations\n\tif isSubagent(messages, req) {\n\t\t// Inject docs hint for subagents if reference docs are indexed\n\t\tif hint := s.getDocsHint(proj); hint != \"\" {\n\t\t\tmodified := injectDocsHintForSubagent(messages, hint)\n\t\t\treq[\"messages\"] = modified\n\t\t\tif newBody, err := json.Marshal(req); err == nil {\n\t\t\t\tbody = newBody\n\t\t\t}\n\t\t\ts.logger.Printf(\"%s[req %d %s tid=%s] %d messages (subagent, docs-hint injected)%s\", colorOrange, reqIdx, proj, threadID, len(messages), colorReset)\n\t\t} else {\n\t\t\ts.logger.Printf(\"%s[req %d %s tid=%s] %d messages (subagent, passthrough)%s\", colorOrange, reqIdx, proj, threadID, len(messages), colorReset)\n\t\t}\n\t\ts.forwardWithAnnotation(w, r, body, reqIdx, toolUseIDs, proj, threadID, msgCount)\n\t\treturn\n\t}\n\n\t// === Sawtooth Cache Optimization ===\n\t// When enabled: use frozen stubs between stub-cycles for cache hits.\n\t// When disabled: fall back to progressive decay on every request above threshold.\n\n\tif s.cfg.SawtoothEnabled {\n\t\t// Restore decay state from DB on cold start (before frozen stubs use it)\n\t\ts.decay.LoadFromDB(threadID)\n\n\t\t// Check for existing frozen stubs first\n\t\tfrozen := s.frozenStubs.Get(threadID, messages)\n\t\tif frozen != nil {\n\t\t\t// Estimate combined token count: frozen prefix + fresh tail + overhead\n\t\t\tfreshMessages := messages[frozen.Cutoff:]\n\t\t\tfreshTokens := s.countMessageTokens(freshMessages)\n\t\t\tcombinedTokens := frozen.Tokens + freshTokens + overhead\n\n\t\t\tif combinedTokens > s.effectiveTokenThreshold(model) ||\n\t\t\t\ts.sawtoothTrigger.GetLastTokens(threadID) > s.effectiveTokenThreshold(model) {\n\t\t\t\t// Fresh tail grew too large — invalidate and re-stub\n\t\t\t\ts.logger.Printf(\"[req %d %s tid=%s] SAWTOOTH: frozen prefix expired (%dk frozen + %dk fresh + %dk overhead = %dk > %dk threshold)\",\n\t\t\t\t\treqIdx, proj, threadID, frozen.Tokens/1000, freshTokens/1000, overhead/1000, combinedTokens/1000, s.effectiveTokenThreshold(model)/1000)\n\t\t\t\ts.frozenStubs.Invalidate(threadID)\n\t\t\t\tfrozen = nil // fall through to trigger check below\n\t\t\t} else {\n\t\t\t\t// Use frozen prefix + fresh tail\n\t\t\t\tcombined := make([]any, 0, len(frozen.Messages)+len(freshMessages))\n\t\t\t\tcombined = append(combined, frozen.Messages...)\n\t\t\t\tcombined = append(combined, freshMessages...)\n\t\t\t\treq[\"messages\"] = combined\n\t\t\t\tneedsReserialization = true\n\t\t\t\ts.logger.Printf(\"%s[req %d %s tid=%s] SAWTOOTH: frozen prefix (%d stubs, %dk msg) + %d fresh (%dk msg) + %dk overhead = %dk total%s\",\n\t\t\t\t\tcolorGreen, reqIdx, proj, threadID, len(frozen.Messages), frozen.Tokens/1000, len(freshMessages), freshTokens/1000, overhead/1000, combinedTokens/1000, colorReset)\n\t\t\t\trawMsgTokens := s.countMessageTokens(messages)\n\t\t\t\ts.setRawEstimate(threadID, rawMsgTokens+overhead)\n\t\t\t\ts.logger.Printf(\"[req %d %s tid=%s] RAW ESTIMATE: msgTokens=%dk + overhead=%dk = %dk (messages=%d)\", reqIdx, proj, threadID, rawMsgTokens/1000, overhead/1000, (rawMsgTokens+overhead)/1000, len(messages))\n\t\t\t\tif InjectFrozenStubCacheBreakpoint(req, len(frozen.Messages)) {\n\t\t\t\t\ts.logger.Printf(\"[req %d %s tid=%s] SAWTOOTH: frozen stub cache breakpoint at messages[%d]\",\n\t\t\t\t\t\treqIdx, proj, threadID, len(frozen.Messages)-1)\n\t\t\t\t}\n\n\t\t\t\t// Ensure frozen stubs are persisted (may have failed on first request after deploy)\n\t\t\t\ts.ensureFrozenPersisted(threadID)\n\t\t\t}\n\t\t}\n\t\tif frozen == nil {\n\t\t\t// No frozen stubs — check trigger\n\t\t\ttriggerReason := s.sawtoothTrigger.ShouldTrigger(threadID, totalTokens)\n\t\t\tif triggerReason != TriggerNone {\n\t\t\t\ts.logger.Printf(\"[req %d %s tid=%s] SAWTOOTH stub-cycle triggered: %s (tokens=%dk)\",\n\t\t\t\t\treqIdx, proj, threadID, triggerReason, totalTokens/1000)\n\n\t\t\t\t// Refresh briefing during stub-cycle — disabled (briefing now via SessionStart hook)\n\t\t\t\t// s.refreshBriefing(proj)\n\t\t\t\t// if s.briefingText != \"\" {\n\t\t\t\t// \tUpsertSystemBlockCached(req, \"yesmem-briefing\", s.briefingText)\n\t\t\t\t// }\n\n\t\t\t\t// Run full stub pipeline\n\t\t\t\ts.setRawEstimate(threadID, totalTokens)\n\t\t\t\t_ = s.runStubCycle(messages, req, reqIdx, proj, threadID, overhead, totalTokens, userQuery, isRetryReq)\n\n\t\t\t\t// Freeze the result\n\t\t\t\tfinalMessages, _ := req[\"messages\"].([]any)\n\t\t\t\tif finalMessages != nil && len(finalMessages) > 0 {\n\t\t\t\t\t// Boundary = last message of original that maps to last stub\n\t\t\t\t\tcutoff := len(messages)\n\t\t\t\t\tvar boundaryMsg any\n\t\t\t\t\tif cutoff > 0 {\n\t\t\t\t\t\tboundaryMsg = messages[cutoff-1]\n\t\t\t\t\t}\n\t\t\t\t\tfrozenTokens := s.countMessageTokens(finalMessages)\n\t\t\t\t\ts.frozenStubs.Store(threadID, finalMessages, cutoff, boundaryMsg, frozenTokens, totalTokens)\n\t\t\t\t\ts.decay.Persist(threadID)\n\t\t\t\t\ts.logger.Printf(\"[req %d %s tid=%s] SAWTOOTH: frozen %d messages at cutoff=%d (~%dk tokens)\",\n\t\t\t\t\t\treqIdx, proj, threadID, len(finalMessages), cutoff, frozenTokens/1000)\n\t\t\t\t\tif InjectFrozenStubCacheBreakpoint(req, len(finalMessages)) {\n\t\t\t\t\t\ts.logger.Printf(\"[req %d %s tid=%s] SAWTOOTH: frozen stub cache breakpoint at messages[%d]\",\n\t\t\t\t\t\t\treqIdx, proj, threadID, len(finalMessages)-1)\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tneedsReserialization = true\n\t\t\t} else {\n\t\t\t\t// No trigger, no frozen stubs — pass through (early conversation)\n\t\t\t\ts.logger.Printf(\"%s[req %d %s tid=%s] %d messages, ~%dk tokens (sawtooth: no trigger)%s\",\n\t\t\t\t\tcolorDim, reqIdx, proj, threadID, len(messages), totalTokens/1000, colorReset)\n\t\t\t}\n\t\t}\n\t} else {\n\t\t// Legacy path: progressive decay\n\t\tif s.shouldStub(totalTokens, model) {\n\t\t\ts.setRawEstimate(threadID, totalTokens)\n\t\t\t_ = s.runStubCycle(messages, req, reqIdx, proj, threadID, overhead, totalTokens, userQuery, isRetryReq)\n\t\t\tneedsReserialization = true\n\t\t}\n\t}\n\n\t// Associative context injection (for ALL requests, not just stubbed ones)\n\t// Re-enabled with higher thresholds (55/75, max 1) after disabling Fresh Remember.\n\tassocContext := s.findAssociativeContextFor(userQuery, proj, threadID, messages)\n\tif assocContext != \"\" {\n\t\tcurrentMessages, _ := req[\"messages\"].([]any)\n\t\tif currentMessages == nil {\n\t\t\tcurrentMessages = messages\n\t\t}\n\t\treq[\"messages\"] = injectAssociativeContext(currentMessages, assocContext, s.cfg.SawtoothEnabled)\n\t\tneedsReserialization = true\n\t\ts.logger.Printf(\"%s[req %d %s tid=%s] associative context injected%s\", colorBlue, reqIdx, proj, threadID, colorReset)\n\t}\n\n\t// Doc-chunk context injection: separate search path for indexed docs (not skills)\n\tdocContext := s.findDocContextFor(userQuery, proj, messages)\n\tif docContext != \"\" {\n\t\tcurrentMessages, _ := req[\"messages\"].([]any)\n\t\tif currentMessages == nil {\n\t\t\tcurrentMessages = messages\n\t\t}\n\t\treq[\"messages\"] = injectAssociativeContext(currentMessages, docContext, s.cfg.SawtoothEnabled)\n\t\tneedsReserialization = true\n\t\ts.logger.Printf(\"%s[req %d %s tid=%s] doc context injected%s\", colorBlue, reqIdx, proj, threadID, colorReset)\n\t}\n\n\t// Fresh Remember Injection: DISABLED\n\t// Causes echo-loop: Claude saves via remember(), sees it again next turn as\n\t// [yesmem fresh memory], reacts to own output instead of user's request.\n\t// Recovery after /clear is handled by recovery.go (briefing), not here.\n\t// Subagents get their own briefing. No remaining use-case.\n\t// remItems := s.popRecentRemember()\n\tvar remItems []recentLearningItem\n\n\t// Rules re-injection: inject condensed CLAUDE.md rules every ~40k output tokens\n\tif rulesBlock := s.rulesInject(threadID, totalTokens, proj); rulesBlock != \"\" {\n\t\tcurrentMessages, _ := req[\"messages\"].([]any)\n\t\tif currentMessages == nil {\n\t\t\tcurrentMessages = messages\n\t\t}\n\t\treq[\"messages\"] = injectAssociativeContext(currentMessages, s.formatRulesReminder(rulesBlock, proj), s.cfg.SawtoothEnabled)\n\t\tneedsReserialization = true\n\t\ts.logger.Printf(\"%s[req %d %s tid=%s] rules reminder injected%s\", colorBlue, reqIdx, proj, threadID, colorReset)\n\t}\n\n\t// Plan auto-activation: nudge Claude to call set_plan() when reading a plan file\n\tif planFile := detectPlanFileRead(messages); planFile != \"\" {\n\t\tif activePlan, _ := s.getActivePlan(threadID); shouldNudgePlan(planFile, activePlan) {\n\t\t\tAppendSystemBlock(req, \"plan-nudge\",\n\t\t\t\tfmt.Sprintf(\"Du hast einen Implementierungsplan gelesen (%s). Aktiviere ihn jetzt via set_plan() damit Plan-Checkpoints funktionieren und dein Fortschritt getrackt wird.\", filepath.Base(planFile)))\n\t\t\tneedsReserialization = true\n\t\t\ts.logger.Printf(\"%s[req %d %s tid=%s] plan nudge injected for %s%s\", colorBlue, reqIdx, proj, threadID, filepath.Base(planFile), colorReset)\n\t\t}\n\t}\n\n\t// Plan checkpoint: inject plan reminder every ~20k tokens if active plan exists\n\ts.detectPlanToolCall(messages, threadID, totalTokens)\n\tif checkpoint := s.planCheckpointInject(threadID, totalTokens); checkpoint != \"\" {\n\t\tcurrentMessages, _ := req[\"messages\"].([]any)\n\t\tif currentMessages == nil {\n\t\t\tcurrentMessages = messages\n\t\t}\n\t\treq[\"messages\"] = injectAssociativeContext(currentMessages, checkpoint, s.cfg.SawtoothEnabled)\n\t\tneedsReserialization = true\n\t\ts.logger.Printf(\"%s[req %d %s tid=%s] plan checkpoint injected%s\", colorBlue, reqIdx, proj, threadID, colorReset)\n\t}\n\n\t// Docs hint injection: inject docs-available reminder every ~10k tokens (independent of plan)\n\tif docsHint := s.docsHintInject(threadID, totalTokens, proj); docsHint != \"\" {\n\t\tcurrentMessages, _ := req[\"messages\"].([]any)\n\t\tif currentMessages == nil {\n\t\t\tcurrentMessages = messages\n\t\t}\n\t\treq[\"messages\"] = injectAssociativeContext(currentMessages, docsHint, s.cfg.SawtoothEnabled)\n\t\tneedsReserialization = true\n\t\ts.logger.Printf(\"%s[req %d %s tid=%s] docs hint injected%s\", colorBlue, reqIdx, proj, threadID, colorReset)\n\t}\n\tif len(remItems) > 0 {\n\t\tvar lines []string\n\t\tfor _, item := range remItems {\n\t\t\tif item.ID > 0 {\n\t\t\t\tlines = append(lines, fmt.Sprintf(\"- [ID:%d] %s\", item.ID, truncateStr(item.Text, 200)))\n\t\t\t} else {\n\t\t\t\tlines = append(lines, \"- \"+truncateStr(item.Text, 200))\n\t\t\t}\n\t\t}\n\t\tremContext := fmt.Sprintf(\"[yesmem fresh memory]\\nGerade gelernt:\\n%s\\n%s\\n[/yesmem fresh memory]\", strings.Join(lines, \"\\n\"), InlineReflectionHint)\n\t\tcurrentMessages, _ := req[\"messages\"].([]any)\n\t\tif currentMessages == nil {\n\t\t\tcurrentMessages = messages\n\t\t}\n\t\treq[\"messages\"] = injectAssociativeContext(currentMessages, remContext, s.cfg.SawtoothEnabled)\n\t\tneedsReserialization = true\n\t\ts.logger.Printf(\"%s[req %d %s tid=%s] fresh remember injected (%d items)%s\", colorBlue, reqIdx, proj, threadID, len(remItems), colorReset)\n\t}\n\n\t// Skill eval injection: mandatory evaluation instruction for real user input only\n\tif threadID != \"\" && proj != \"\" && isUserInputTurn(messages) {\n\t\t// First: detect any skill activations from message history (updates tracker)\n\t\ts.syncSkillActivations(messages, proj, threadID)\n\n\t\t// Inject mandatory skill evaluation block into last user message\n\t\tskillEval := buildSkillEvalBlock()\n\t\tcurrentMessages, _ := req[\"messages\"].([]any)\n\t\tif currentMessages == nil {\n\t\t\tcurrentMessages = messages\n\t\t}\n\t\treq[\"messages\"] = injectAssociativeContext(currentMessages, skillEval, s.cfg.SawtoothEnabled)\n\t\tneedsReserialization = true\n\t\ts.logger.Printf(\"%s[req %d %s tid=%s] skill-eval injected%s\", colorBlue, reqIdx, proj, threadID, colorReset)\n\t}\n\n\t// === Collect injected learning IDs for next turn's inline reflection ===\n\tif threadID != \"\" {\n\t\tcurrentIDs := make(map[int64]string) // id → source\n\t\t// Source 1: Briefing learnings (cached, same every turn)\n\t\ts.briefingMu.RLock()\n\t\tbriefingSnapshot := s.briefingText\n\t\ts.briefingMu.RUnlock()\n\t\tif briefingSnapshot != \"\" {\n\t\t\tfor _, m := range idPattern.FindAllStringSubmatch(briefingSnapshot, -1) {\n\t\t\t\tif id, err := strconv.ParseInt(m[1], 10, 64); err == nil {\n\t\t\t\t\tcurrentIDs[id] = \"briefing\"\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\t// Source 2: Associative context (contextual, per-turn)\n\t\tif assocContext != \"\" {\n\t\t\tfor _, m := range idPattern.FindAllStringSubmatch(assocContext, -1) {\n\t\t\t\tif id, err := strconv.ParseInt(m[1], 10, 64); err == nil {\n\t\t\t\t\tcurrentIDs[id] = \"associative\" // overwrites briefing if both — associative is the stronger signal\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\t// Source 3: Fresh remember items (just learned)\n\t\tfor _, item := range remItems {\n\t\t\tif item.ID > 0 {\n\t\t\t\tcurrentIDs[item.ID] = \"fresh\"\n\t\t\t}\n\t\t}\n\t\ts.lastInjectedIDsMu.Lock()\n\t\tif len(currentIDs) > 0 {\n\t\t\t// Accumulate IDs across turns — don't overwrite.\n\t\t\t// Claude may reference an ID from turn 30 in turn 50.\n\t\t\texisting := s.lastInjectedIDs[threadID]\n\t\t\tif existing == nil {\n\t\t\t\texisting = make(map[int64]string)\n\t\t\t}\n\t\t\tfor id, source := range currentIDs {\n\t\t\t\texisting[id] = source\n\t\t\t}\n\t\t\ts.lastInjectedIDs[threadID] = existing\n\t\t}\n\t\ts.lastInjectedIDsMu.Unlock()\n\t}\n\n\t// Think reminder injection — only for real user turns, not tool continuations\n\tif threadID != \"\" {\n\t\tcurrentMessages, _ := req[\"messages\"].([]any)\n\t\tif currentMessages == nil {\n\t\t\tcurrentMessages = messages\n\t\t}\n\t\tif lastUserHasText(currentMessages) {\n\t\t\tthinkReminder := s.buildThinkReminder(threadID, threadID, false)\n\t\t\tif thinkReminder != \"\" {\n\t\t\t\treq[\"messages\"] = injectAssociativeContext(currentMessages, thinkReminder, s.cfg.SawtoothEnabled)\n\t\t\t\tneedsReserialization = true\n\t\t\t}\n\t\t}\n\n\t\t// Dialog injection: append at end of messages with pseudo-padding for alternation.\n\t\t// Always works regardless of tool_result chains or conversation state.\n\t\tdr := s.checkDialogMessages(threadID, proj)\n\t\tif dr.Extra != \"\" {\n\t\t\t// Prepend session ID for agent-to-agent communication\n\t\t\tdr.Extra = fmt.Sprintf(\"DEINE_SESSION_ID: %s\\n\", threadID) + dr.Extra\n\t\t\tinjected := false\n\t\t\tif msgs, ok := req[\"messages\"].([]any); ok && len(msgs) > 0 {\n\t\t\t\t// Pad with empty assistant if last message is user (maintains alternation)\n\t\t\t\tlastMsg, _ := msgs[len(msgs)-1].(map[string]any)\n\t\t\t\tif lastMsg != nil && lastMsg[\"role\"] == \"user\" {\n\t\t\t\t\tmsgs = append(msgs, map[string]any{\n\t\t\t\t\t\t\"role\": \"assistant\",\n\t\t\t\t\t\t\"content\": \"​\", // zero-width space — invisible padding\n\t\t\t\t\t})\n\t\t\t\t}\n\t\t\t\t// Append dialog as new user message (becomes the last message Claude sees)\n\t\t\t\tmsgs = append(msgs, map[string]any{\n\t\t\t\t\t\"role\": \"user\",\n\t\t\t\t\t\"content\": \"\\n\" + dr.Extra + \"\\n\",\n\t\t\t\t})\n\t\t\t\treq[\"messages\"] = msgs\n\t\t\t\tneedsReserialization = true\n\t\t\t\tinjected = true\n\t\t\t}\n\t\t\t// Mark as read after 3 injection turns — long enough for Claude to process,\n\t\t\t// resilient against fast ping-pong sessions (time-based was broken for <30s turns).\n\t\t\tif injected && dr.HasUnread && dr.SessionID != \"\" {\n\t\t\t\tif s.shouldMarkChannelRead(dr) {\n\t\t\t\t\ts.markDialogRead(dr)\n\t\t\t\t}\n\t\t\t}\n\t\t\ts.logger.Printf(\"[dialog] injection injected=%v extra_len=%d\", injected, len(dr.Extra))\n\t\t}\n\t}\n\n\t// Signal tools are NOT injected into the main request.\n\t// They are collected via a separate async reflection call after the response.\n\t// See fireReflectionCall() in reflection.go.\n\t// NEVER put _signal_* tools back here — the model ignores them among 44+ real tools.\n\n\t// Metadata injection: timestamp, message index, time-delta for messages after cache breakpoint.\n\t// Messages before the breakpoint are cached — modifying them would break the cache.\n\tif threadID != \"\" {\n\t\tif msgs, ok := req[\"messages\"].([]any); ok && len(msgs) > 0 {\n\t\t\tnow := time.Now()\n\t\t\tnowStr := shortWeekday(now.Weekday()) + \" \" + now.Format(\"2006-01-02 15:04:05\")\n\n\t\t\t// Find last cache_control breakpoint in messages\n\t\t\tcacheIdx := -1\n\t\t\tfor i, m := range msgs {\n\t\t\t\tmsg, _ := m.(map[string]any)\n\t\t\t\tif msg == nil {\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\t\t\t\tif _, has := msg[\"cache_control\"]; has {\n\t\t\t\t\tcacheIdx = i\n\t\t\t\t}\n\t\t\t\tif content, ok := msg[\"content\"].([]any); ok {\n\t\t\t\t\tfor _, block := range content {\n\t\t\t\t\t\tif b, ok := block.(map[string]any); ok {\n\t\t\t\t\t\t\tif _, has := b[\"cache_control\"]; has {\n\t\t\t\t\t\t\t\tcacheIdx = i\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tstartIdx := cacheIdx + 1\n\t\t\tif startIdx < 0 {\n\t\t\t\tstartIdx = 0\n\t\t\t}\n\n\t\t\t// Retrieve stored response time for assistant annotation\n\t\t\ts.responseTsMu.RLock()\n\t\t\trespTime, hasRespTime := s.responseTimes[threadID]\n\t\t\ts.responseTsMu.RUnlock()\n\n\t\t\t// 1. Last user message: annotate with [2026-03-13 00:15:42] [msg:N]\n\t\t\tlastMsg, _ := msgs[len(msgs)-1].(map[string]any)\n\t\t\tif lastMsg != nil && lastMsg[\"role\"] == \"user\" {\n\t\t\t\tmeta := fmt.Sprintf(\"[%s] [msg:%d]\", nowStr, s.msgCounters.nextFor(threadID, len(msgs)-1))\n\t\t\t\tif hasRespTime {\n\t\t\t\t\tdelta := now.Sub(respTime)\n\t\t\t\t\tmeta += \" [+\" + formatDelta(delta) + \"]\"\n\t\t\t\t}\n\t\t\t\tprependMeta(lastMsg, meta)\n\t\t\t\tneedsReserialization = true\n\t\t\t}\n\n\t\t\t// 2. Last assistant message after cache breakpoint: use stored response time\n\t\t\tif hasRespTime {\n\t\t\t\tfor i := len(msgs) - 1; i >= startIdx; i-- {\n\t\t\t\t\tmsg, _ := msgs[i].(map[string]any)\n\t\t\t\t\tif msg != nil && msg[\"role\"] == \"assistant\" {\n\t\t\t\t\t\tmeta := fmt.Sprintf(\"[%s] [msg:%d]\", respTime.Format(\"2006-01-02 15:04:05\"), i)\n\t\t\t\t\t\tprependMeta(msg, meta)\n\t\t\t\t\t\tneedsReserialization = true\n\t\t\t\t\t\tbreak\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\t// Self-Priming: DISABLED (experimental, see signal_handlers.go comment block).\n\t// The reflection-based selfprime (Haiku) was too inaccurate to be useful —\n\t// the main model adapts to user context organically from message history.\n\t// Kept for reference; re-enable by uncommenting below + setting ShouldActivate=true.\n\t//\n\t// s.selfPrimeMu.RLock()\n\t// anchor := s.selfPrimes[threadID]\n\t// s.selfPrimeMu.RUnlock()\n\t// if anchor != \"\" {\n\t// \tif msgs, ok := req[\"messages\"].([]any); ok && len(msgs) > 0 {\n\t// \t\tlastMsg, _ := msgs[len(msgs)-1].(map[string]any)\n\t// \t\tif lastMsg != nil && lastMsg[\"role\"] == \"user\" {\n\t// \t\t\tprimeBlock := map[string]any{\n\t// \t\t\t\t\"type\": \"text\",\n\t// \t\t\t\t\"text\": \"[self-prime]\\n[Self-Prime von deiner letzten Antwort]: \" + anchor,\n\t// \t\t\t}\n\t// \t\t\tif content, ok := lastMsg[\"content\"].([]any); ok {\n\t// \t\t\t\tlastMsg[\"content\"] = append(content, primeBlock)\n\t// \t\t\t} else if text, ok := lastMsg[\"content\"].(string); ok {\n\t// \t\t\t\tlastMsg[\"content\"] = []any{\n\t// \t\t\t\t\tmap[string]any{\"type\": \"text\", \"text\": text},\n\t// \t\t\t\t\tprimeBlock,\n\t// \t\t\t\t}\n\t// \t\t\t}\n\t// \t\t\tneedsReserialization = true\n\t// \t\t}\n\t// \t}\n\t// }\n\n\t// Prompt Caching: inject cache_control breakpoints on system + tools\n\t// Sawtooth: disabled (all 4 slots are used by CC system (2) + briefing (1) + CC messages (1))\n\t// Legacy: count existing breakpoints and fill up remaining slots.\n\tif !s.cfg.SawtoothEnabled && s.cacheGate.ShouldCache() {\n\t\tif n := InjectCacheBreakpoints(req, s.logger); n > 0 {\n\t\t\tneedsReserialization = true\n\t\t\ts.logger.Printf(\"[req %d %s] prompt cache: %d breakpoints injected\", reqIdx, proj, n)\n\t\t} else {\n\t\t\ts.logger.Printf(\"[req %d %s] prompt cache: no budget (4/4 already set)\", reqIdx, proj)\n\t\t}\n\t}\n\n\t// Final: upgrade cache TTL + enforce breakpoint limit (must run AFTER all injections + InjectCacheBreakpoints)\n\t// TTL upgrade only for API key auth — subscription (OAuth) users should not get extended TTL.\n\tif s.cfg.CacheTTL != \"\" && s.cfg.CacheTTL != \"ephemeral\" && IsAPIKeyAuth(r.Header) {\n\t\tif n := UpgradeCacheTTL(req, s.cfg.CacheTTL); n > 0 {\n\t\t\tneedsReserialization = true\n\t\t\ts.logger.Printf(\"[req %d %s] cache TTL upgraded: %d blocks → %s\", reqIdx, proj, n, s.cfg.CacheTTL)\n\t\t}\n\t}\n\tif n := EnforceCacheBreakpointLimit(req, maxCacheBreakpoints); n > 0 {\n\t\tneedsReserialization = true\n\t\ts.logger.Printf(\"[req %d %s] prompt cache: trimmed %d surplus breakpoints\", reqIdx, proj, n)\n\t}\n\n\t// Pre-flight: validate tool_use/tool_result pairing before forwarding.\n\t// Repairs orphans created by Stubify/Collapse/Injection pipeline.\n\tif msgs, ok := req[\"messages\"].([]any); ok {\n\t\trepaired, orphans := validateToolPairs(msgs, s.logger)\n\t\tif orphans > 0 {\n\t\t\treq[\"messages\"] = repaired\n\t\t\ts.logger.Printf(\"%s[req %d %s tid=%s] validate: repaired %d orphan tool_result(s)%s\", colorOrange, reqIdx, proj, threadID, orphans, colorReset)\n\t\t\tneedsReserialization = true\n\t\t}\n\t}\n\n\t// Early-out: skip re-serialization when nothing changed\n\tif !needsReserialization {\n\t\ts.logger.Printf(\"%s[req %d %s tid=%s] %d messages, ~%dk tokens (no stubbing, threshold=%dk)%s\", colorDim, reqIdx, proj, threadID, len(messages), totalTokens/1000, s.effectiveTokenThreshold(model)/1000, colorReset)\n\t\ts.forwardWithAnnotation(w, r, body, reqIdx, toolUseIDs, proj, threadID, msgCount, totalTokens)\n\t\treturn\n\t}\n\n\t// Re-serialize\n\tbody, err = json.Marshal(req)\n\tif err != nil {\n\t\ts.logger.Printf(\"%s[req %d %s tid=%s] re-marshal error: %v%s\", colorRed, reqIdx, proj, threadID, err, colorReset)\n\t\thttp.Error(w, \"internal proxy error\", http.StatusInternalServerError)\n\t\treturn\n\t}\n\n\t// Debug: dump final request body to file (enable with YESMEM_PROXY_DEBUG=1)\n\tif needsReserialization && os.Getenv(\"YESMEM_PROXY_DEBUG\") == \"1\" {\n\t\tdumpDir := filepath.Join(s.cfg.DataDir, \"debug\")\n\t\tos.MkdirAll(dumpDir, 0755)\n\t\tts := time.Now().Format(\"20060102-150405\")\n\t\tdumpPath := fmt.Sprintf(\"%s/req_%s_%d.json\", dumpDir, ts, reqIdx)\n\t\tos.WriteFile(dumpPath, body, 0644)\n\t\ts.logger.Printf(\"[req %d] dumped %dk to %s\", reqIdx, len(body)/1024, dumpPath)\n\t}\n\n\t// Task #7: Mark this request fingerprint for retry detection\n\tif !isRetryReq {\n\t\ts.markRequest(fp)\n\t}\n\n\t// Forward to Anthropic with SSE annotation extraction\n\t// Use original msgCount (pre-injection) so actual-based estimation stays consistent across turns\n\t// Use actual-based totalTokens as estimate when available (more accurate than full recount)\n\tfinalEstimate := totalTokens\n\tif s.sawtoothTrigger == nil || s.sawtoothTrigger.GetLastTokens(threadID) == 0 {\n\t\t// First request: no actual yet, do a full recount of post-injection messages\n\t\tfinalMsgs, _ := req[\"messages\"].([]any)\n\t\tfinalEstimate = s.measureOverhead(req) + s.countMessageTokens(finalMsgs)\n\t}\n\ts.logger.Printf(\"[req %d %s tid=%s] FINAL: ~%dk estimated (after all injections)\", reqIdx, proj, threadID, finalEstimate/1000)\n\tif raw := s.getRawEstimate(threadID); raw > 0 {\n\t\ts.cacheStatusWriter.UpdateRawForThread(threadID, raw)\n\t}\n\ts.forwardWithAnnotation(w, r, body, reqIdx, toolUseIDs, proj, threadID, msgCount, finalEstimate)\n}\n\n// bytesPerToken is the approximation factor for token estimation.\n// Anthropic recommends ~3.5 chars/token; we use 3.6 for slight overestimation\n// (triggers stubbing a bit earlier = safer for context budget).\nconst bytesPerToken = 3.6\n\n// TokenEstimateFunc is the central token estimation function.\n// All stubbing/budget decisions use this. Receives extracted text content\n// (not JSON wire format) so the tokenizer counts actual content tokens.\ntype TokenEstimateFunc func(text string) int\n"}, "da0883fd718f87ca920be8525cbf35e9a9f7619e": {"t": 1785759379.140571, "n": 51329, "h": "78a27ec1dd10043b", "c": "package proxy\n\nimport (\n\t\"context\"\n\t\"crypto/tls\"\n\t\"encoding/json\"\n\t\"fmt\"\n\t\"io\"\n\t\"log\"\n\t\"net\"\n\t\"net/http\"\n\t\"os\"\n\t\"os/signal\"\n\t\"path/filepath\"\n\t\"strconv\"\n\t\"strings\"\n\t\"sync\"\n\t\"sync/atomic\"\n\t\"syscall\"\n\t\"time\"\n\n\ttokenizer \"github.com/qhenkart/anthropic-tokenizer-go\"\n)\n\n// Config holds proxy configuration.\ntype Config struct {\n\tListenAddr string // e.g. \":9099\"\n\tTargetURL string // e.g. \"https://api.anthropic.com\"\n\tTokenThreshold int // trigger stubbing above this estimated token count\n\tTokenMinimumThreshold int // stub down to this floor\n\tTokenThresholds map[string]int // model-specific thresholds: {\"opus\": 180000, \"haiku\": 130000}\n\tKeepRecent int // number of messages to always keep unmodified\n\tDataDir string // yesmem data directory for DB access\n\tOpenAITargetURL string // upstream for OpenAI-format clients; if empty, uses TargetURL\n\n\t// Signal reflection\n\tSignalsEnabled bool // enable async signal reflection calls\n\tSignalsMode string // \"reflection\" = separate API call\n\tSignalsEveryNTurns int // every N end_turn responses\n\tSignalsModel string // full model ID, e.g. \"claude-sonnet-4-6\"\n\tAPIKey string // needed for reflection API calls\n\n\t// Sawtooth cache optimization\n\tSawtoothEnabled bool // use sawtooth instead of progressive decay (default: true)\n\tCacheTTL string // \"ephemeral\" (5m) or \"1h\" (extended)\n\n\t// Usage deflation: scale down input_tokens reported to CC to suppress \"Context low\" warning.\n\t// 0 = disabled, 0.7 = report 70% of actual tokens. Real values kept for internal tracking.\n\tUsageDeflationFactor float64\n\n\t// System prompt rewriting\n\tPromptUngate bool // strip \"may or may not be relevant\" disclaimer from CLAUDE.md injection\n\tPromptRewrite bool // strip \"Output efficiency\" + \"short and concise\", inject Ant-quality directives\n\tPromptEnhance bool // CLAUDE.md authority reinforcement, comment discipline, persona-based tone\n\n\t// Cache keepalive\n\tCacheKeepaliveEnabled bool\n\tCacheKeepaliveMode string // \"auto\", \"5m\", \"1h\"\n\tCacheKeepalivePings5m int\n\tCacheKeepalivePings1h int\n\n\t// Forked agents\n\tForkedAgentsEnabled bool\n\tForkedAgentsModel string // full model ID\n\tForkedAgentsTokenGrowthTrigger int\n\tForkedAgentsMaxFailures int\n\tForkedAgentsMaxForksPerSession int\n\tForkedAgentsDebug bool\n\n\t// Cache state management\n\tResetCache bool // clear persisted frozen stubs and decay state on startup\n}\n\nconst maxAnnotations = 5000 // evict oldest when exceeded\n\n// ANSI color codes for log output\nconst (\n\tcolorReset = \"\\033[0m\"\n\tcolorDim = \"\\033[2m\" // gray — passthrough / no stubbing\n\tcolorGreen = \"\\033[32m\" // stubbed / compressed\n\tcolorLightGreen = \"\\033[92m\" // FINAL without compression\n\tcolorBlue = \"\\033[34m\" // injection\n\tcolorOrange = \"\\033[33m\" // skip / retry\n\tcolorRed = \"\\033[31m\" // errors\n)\n\n// Server is the infinite-thread proxy.\ntype Server struct {\n\tcfg Config\n\thttpClient *http.Client\n\tlogger *log.Logger\n\trequestIdx atomic.Int64\n\n\t// Post-hoc annotations: tool_use_id → first ~120 chars of Claude's response\n\tmu sync.RWMutex\n\tannotations map[string]string\n\n\t// Progressive decay tracker\n\tdecay *DecayTracker\n\n\t// Living narrative block\n\tnarrative *Narrative\n\n\t// Pivot moments cache (from daemon)\n\tpivotMu sync.RWMutex\n\tpivotTexts []string\n\tpivotCached time.Time\n\n\t// Aggregate stats (Task #2)\n\tstats *ProxyStats\n\n\t// Local tokenizer for accurate token counting\n\ttokenizer *tokenizer.Tokenizer\n\n\t// Cached overhead tokens (system + tools), set on first request per thread\n\toverheadMu sync.RWMutex\n\toverheadTokens int // unused, kept for interface compat\n\n\t// Hysteresis state for stubbing (Task #6)\n\tstubActive atomic.Bool\n\n\t// Daemon connection state — first successful connect flips to true\n\tdaemonReady atomic.Bool\n\n\t// Retry detection (Task #7)\n\tretryMu sync.RWMutex\n\tlastFingerprint string\n\n\t// Session start time for temporal annotation in archive blocks\n\tsessionStartMu sync.RWMutex\n\tsessionStartTime time.Time\n\n\t// Runtime config override (set via MCP set_config tool)\n\tconfigOverrideMu sync.RWMutex\n\ttokenThresholdOverrides map[string]int // model-key → threshold, \"\" = global fallback\n\n\t// Briefing cache — loaded once per thread, injected as user/assistant message pair\n\tbriefingMu sync.RWMutex\n\tbriefingText string\n\n\t// Cognitive signal bus — routes _signal_* tool calls to handlers\n\tsignalBus *SignalBus\n\n\t// Inline reflection: track which learning IDs were injected per thread\n\tlastInjectedIDsMu sync.Mutex\n\tlastInjectedIDs map[string]map[int64]string // threadID → id → source (\"briefing\"|\"associative\"|\"fresh\")\n\tsessionInjectCounts map[string]map[int64]int // threadID → learningID → injection count this session\n\tlastTurnInjected map[string]map[int64]bool // threadID → learningIDs injected in previous turn\n\n\t// Self-priming anchors: threadID → last self-prime text\n\tselfPrimeMu sync.RWMutex\n\tselfPrimes map[string]string\n\n\t// Timestamp tracking: threadID → time of last response completion\n\tresponseTsMu sync.RWMutex\n\tresponseTimes map[string]time.Time\n\n\t// Think reminder: per-thread request counter (replaces hook-think file-based counter)\n\tthinkMu sync.Mutex\n\tthinkCounters map[string]int // threadID → request count\n\n\tchannelMu sync.Mutex\n\tchannelInjectCount map[string]int // sessionID → injection turn count\n\n\t// Prompt cache gating — enables cache_control breakpoints when requests are frequent\n\tcacheGate *CacheGate\n\n\t// Sawtooth cache optimization\n\tfrozenStubs *FrozenStubs\n\tsawtoothTrigger *SawtoothTrigger\n\tcacheStatusWriter *CacheStatusWriter\n\tcacheKeepalive *CacheKeepalive\n\tcacheTTLDetector *CacheTTLDetector\n\n\t// Cumulative token savings from collapsing (per threadID)\n\trawSavingsMu sync.Mutex\n\trawSavings map[string]int\n\n\t// Skill hint tracking: per-thread active skills\n\tskillTracker *skillHintTracker\n\n\t// Forked agent state: per-thread token growth + failure tracking\n\tforkState *ForkState\n\tforkConfigs []ForkConfig\n\n\t// Rules re-injection: condensed CLAUDE.md rules injected every ~40k tokens\n\trulesMu sync.RWMutex\n\trulesBlock string // cached condensed rules (fetched once from daemon)\n\trulesTokenCount map[string]int // threadID → tokens since last rules injection\n\trulesCollapseInjected map[string]bool // threadID → true if collapse already injected rules (reset by normal inject)\n\tmsgCounters *msgCounters // global per-thread msg counter, persists across collapses\n\n\t// Loop detection: per-thread warning state\n\tloopMu sync.Mutex\n\tloopStates map[string]*LoopState // threadID → state\n}\n\n// Run starts the proxy server and blocks until interrupted.\nfunc Run(cfg Config) error {\n\ts := &Server{\n\t\tcfg: cfg,\n\t\thttpClient: &http.Client{\n\t\t\tTimeout: 10 * time.Minute,\n\t\t\tTransport: &http.Transport{\n\t\t\t\tTLSClientConfig: &tls.Config{},\n\t\t\t\tMaxIdleConns: 10,\n\t\t\t\tIdleConnTimeout: 90 * time.Second,\n\t\t\t\tDisableCompression: true,\n\t\t\t\tResponseHeaderTimeout: 60 * time.Second,\n\t\t\t},\n\t\t},\n\t\tlogger: createLogger(cfg.DataDir),\n\t\tannotations: make(map[string]string),\n\t\tdecay: NewDecayTracker(),\n\t\tnarrative: NewNarrative(),\n\t\tstats: &ProxyStats{startTime: time.Now()},\n\t\tselfPrimes: make(map[string]string),\n\t\tlastInjectedIDs: make(map[string]map[int64]string),\n\t\tsessionInjectCounts: make(map[string]map[int64]int),\n\t\tlastTurnInjected: make(map[string]map[int64]bool),\n\t\tresponseTimes: make(map[string]time.Time),\n\t\tthinkCounters: make(map[string]int),\n\t\tchannelInjectCount: make(map[string]int),\n\t\tcacheGate: NewCacheGate(cacheGapForTTL(cfg.CacheTTL)),\n\t\tfrozenStubs: NewFrozenStubsWithTTL(sawtoothTTLForCacheTTL(cfg.CacheTTL)),\n\t\tsawtoothTrigger: NewSawtoothTrigger(cacheGapForTTL(cfg.CacheTTL), cfg.TokenThreshold, cfg.TokenMinimumThreshold),\n\t\tcacheStatusWriter: NewCacheStatusWriter(cfg.DataDir, cfg.CacheTTL, cfg.TokenThreshold, cfg.TokenMinimumThreshold),\n\t\tcacheTTLDetector: NewCacheTTLDetectorWithPersist(cfg.DataDir),\n\t\tskillTracker: newSkillHintTracker(),\n\t\trulesTokenCount: make(map[string]int),\n\t\trulesCollapseInjected: make(map[string]bool),\n\t\tmsgCounters: newMsgCounters(),\n\t\tloopStates: make(map[string]*LoopState),\n\t\tforkState: NewForkState(cfg.ForkedAgentsTokenGrowthTrigger, cfg.ForkedAgentsMaxFailures, cfg.ForkedAgentsMaxForksPerSession),\n\t\tforkConfigs: []ForkConfig{},\n\t}\n\n\t// Log persisted detection state\n\tif sup := s.cacheTTLDetector.Is1hSupported(); sup != nil {\n\t\ts.logger.Printf(\"[proxy] loaded persisted TTL detection: 1h_supported=%v\", *sup)\n\t\tif *sup && s.frozenStubs != nil {\n\t\t\ts.frozenStubs.UpdateTTL(sawtoothTTLForCacheTTL(\"1h\"))\n\t\t}\n\t}\n\n\tif cfg.CacheKeepaliveEnabled {\n\t\ts.cacheKeepalive = NewCacheKeepalive(CacheKeepaliveConfig{\n\t\t\tTarget: cfg.TargetURL,\n\t\t\tMode: cfg.CacheKeepaliveMode,\n\t\t\tPings5m: cfg.CacheKeepalivePings5m,\n\t\t\tPings1h: cfg.CacheKeepalivePings1h,\n\t\t\tDetector: s.cacheTTLDetector,\n\t\t\tLogger: s.logger,\n\t\t\tOnPing: func(threadID string, cacheRead, cacheWrite int) {\n\t\t\t\ts.cacheTTLDetector.RecordResponse(cacheRead, cacheWrite, 0)\n\t\t\t\tif s.frozenStubs != nil {\n\t\t\t\t\ts.frozenStubs.Touch(threadID)\n\t\t\t\t}\n\t\t\t\tif s.sawtoothTrigger != nil {\n\t\t\t\t\ts.sawtoothTrigger.TouchRequestTime(threadID)\n\t\t\t\t}\n\t\t\t},\n\t\t})\n\t}\n\n\t// Wire detector + keepalive to status writer for terminal display\n\ts.cacheStatusWriter.SetDetector(s.cacheTTLDetector)\n\tif s.cacheKeepalive != nil {\n\t\ts.cacheStatusWriter.SetKeepalive(s.cacheKeepalive)\n\t}\n\n\t// Register forked agent configs\n\tif cfg.ForkedAgentsEnabled {\n\t\ts.forkConfigs = append(s.forkConfigs, NewExtractAndEvaluateConfig(cfg.ForkedAgentsModel))\n\t\tmodelDesc := cfg.ForkedAgentsModel\n\t\tif modelDesc == \"\" {\n\t\t\tmodelDesc = \"(same as main thread)\"\n\t\t}\n\t\ts.logger.Printf(\"[proxy] forked agents enabled: %d configs, model=%s, trigger=%dk tokens\",\n\t\t\tlen(s.forkConfigs), modelDesc, cfg.ForkedAgentsTokenGrowthTrigger/1000)\n\t}\n\n\t// Wire sawtooth token persistence via daemon RPC\n\ts.sawtoothTrigger.SetPersistFunc(func(key, value string) {\n\t\tif _, err := s.queryDaemon(\"set_proxy_state\", map[string]any{\"key\": key, \"value\": value}); err != nil {\n\t\t\ts.logger.Printf(\"[sawtooth] persist failed for %s: %v\", key, err)\n\t\t}\n\t})\n\ts.sawtoothTrigger.SetLoadFunc(func(key string) (string, bool) {\n\t\tresult, err := s.queryDaemon(\"get_proxy_state\", map[string]any{\"key\": key})\n\t\tif err != nil || result == nil {\n\t\t\treturn \"\", false\n\t\t}\n\t\tvar resp struct {\n\t\t\tValue string `json:\"value\"`\n\t\t}\n\t\tif json.Unmarshal(result, &resp) != nil || resp.Value == \"\" {\n\t\t\treturn \"\", false\n\t\t}\n\t\treturn resp.Value, true\n\t})\n\n\t// Wire frozen stubs persistence via same daemon RPC\n\ts.frozenStubs.SetPersistFunc(func(key, value string) {\n\t\tif _, err := s.queryDaemon(\"set_proxy_state\", map[string]any{\"key\": key, \"value\": value}); err != nil {\n\t\t\ts.logger.Printf(\"[frozen] persist failed for %s: %v\", key, err)\n\t\t}\n\t})\n\ts.frozenStubs.SetLoadFunc(func(key string) (string, bool) {\n\t\tresult, err := s.queryDaemon(\"get_proxy_state\", map[string]any{\"key\": key})\n\t\tif err != nil || result == nil {\n\t\t\treturn \"\", false\n\t\t}\n\t\tvar resp struct {\n\t\t\tValue string `json:\"value\"`\n\t\t}\n\t\tif json.Unmarshal(result, &resp) != nil || resp.Value == \"\" {\n\t\t\treturn \"\", false\n\t\t}\n\t\treturn resp.Value, true\n\t})\n\n\t// Wire decay tracker persistence via same daemon RPC\n\ts.decay.SetPersistFunc(func(key, value string) {\n\t\tif _, err := s.queryDaemon(\"set_proxy_state\", map[string]any{\"key\": key, \"value\": value}); err != nil {\n\t\t\ts.logger.Printf(\"[decay] persist failed for %s: %v\", key, err)\n\t\t}\n\t})\n\ts.decay.SetLoadFunc(func(key string) (string, bool) {\n\t\tresult, err := s.queryDaemon(\"get_proxy_state\", map[string]any{\"key\": key})\n\t\tif err != nil || result == nil {\n\t\t\treturn \"\", false\n\t\t}\n\t\tvar resp struct {\n\t\t\tValue string `json:\"value\"`\n\t\t}\n\t\tif json.Unmarshal(result, &resp) != nil || resp.Value == \"\" {\n\t\t\treturn \"\", false\n\t\t}\n\t\treturn resp.Value, true\n\t})\n\n\t// Clear persisted cache state if --reset-cache was passed\n\tif cfg.ResetCache {\n\t\tfor _, prefix := range []string{\"frozen:\", \"decay:\"} {\n\t\t\tresult, err := s.queryDaemon(\"delete_proxy_state_prefix\", map[string]any{\"prefix\": prefix})\n\t\t\tif err != nil {\n\t\t\t\ts.logger.Printf(\"[reset-cache] failed to delete %s*: %v\", prefix, err)\n\t\t\t} else {\n\t\t\t\tvar resp struct{ Deleted int }\n\t\t\t\tjson.Unmarshal(result, &resp)\n\t\t\t\ts.logger.Printf(\"[reset-cache] deleted %d %s* entries\", resp.Deleted, prefix)\n\t\t\t}\n\t\t}\n\t}\n\n\t// Init signal bus with daemon RPC as callback\n\ts.signalBus = NewSignalBus(s.logger)\n\tregisterSignalHandlers(s.signalBus, s.logger, s.queryDaemon, func(threadID, anchor string) {\n\t\ts.selfPrimeMu.Lock()\n\t\ts.selfPrimes[threadID] = anchor\n\t\ts.selfPrimeMu.Unlock()\n\t})\n\n\t// Init local tokenizer (BPE-based, ~60ms)\n\ttok, err := tokenizer.New()\n\tif err != nil {\n\t\ts.logger.Printf(\"%s[proxy] tokenizer init failed, falling back to heuristic: %v%s\", colorOrange, err, colorReset)\n\t} else {\n\t\ts.tokenizer = tok\n\t\ts.logger.Printf(\"[proxy] tokenizer initialized\")\n\t}\n\n\tmux := http.NewServeMux()\n\tmux.HandleFunc(\"/health\", s.handleHealth)\n\tmux.HandleFunc(\"/\", s.handleRequest)\n\n\tsrv := &http.Server{\n\t\tAddr: sanitizeListenAddr(cfg.ListenAddr),\n\t\tHandler: mux,\n\t}\n\n\tdone := make(chan os.Signal, 1)\n\tsignal.Notify(done, syscall.SIGINT, syscall.SIGTERM)\n\n\tgo func() {\n\t\t<-done\n\t\ts.logger.Println(\"shutting down (draining in-flight requests)...\")\n\t\tif s.cacheKeepalive != nil {\n\t\t\ts.cacheKeepalive.Stop()\n\t\t}\n\t\tctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)\n\t\tdefer cancel()\n\t\tif err := srv.Shutdown(ctx); err != nil {\n\t\t\ts.logger.Printf(\"graceful shutdown failed: %v, forcing close\", err)\n\t\t\tsrv.Close()\n\t\t}\n\t}()\n\n\ts.logger.Printf(\"listening on %s → %s (threshold=%d, keepRecent=%d, model_thresholds=%v)\",\n\t\tsanitizeListenAddr(cfg.ListenAddr), cfg.TargetURL, cfg.TokenThreshold, cfg.KeepRecent, cfg.TokenThresholds)\n\n\tif err := srv.ListenAndServe(); err != http.ErrServerClosed {\n\t\treturn fmt.Errorf(\"listen: %w\", err)\n\t}\n\treturn nil\n}\n\n// effectiveTokenThreshold returns the runtime token threshold for a given model.\n// Priority: MCP override (model-specific) > MCP override (global) > config model-specific > config global.\nfunc (s *Server) effectiveTokenThreshold(model string) int {\n\ts.configOverrideMu.RLock()\n\toverrides := s.tokenThresholdOverrides\n\ts.configOverrideMu.RUnlock()\n\tif len(overrides) > 0 && model != \"\" {\n\t\tlower := strings.ToLower(model)\n\t\tfor key, threshold := range overrides {\n\t\t\tif key != \"\" && strings.Contains(lower, strings.ToLower(key)) && threshold > 0 {\n\t\t\t\treturn threshold\n\t\t\t}\n\t\t}\n\t}\n\t// Global override fallback (key \"\")\n\tif len(overrides) > 0 {\n\t\tif globalOverride, ok := overrides[\"\"]; ok && globalOverride > 0 {\n\t\t\treturn globalOverride\n\t\t}\n\t}\n\tif model != \"\" && len(s.cfg.TokenThresholds) > 0 {\n\t\tlower := strings.ToLower(model)\n\t\tfor key, threshold := range s.cfg.TokenThresholds {\n\t\t\tif strings.Contains(lower, strings.ToLower(key)) {\n\t\t\t\treturn threshold\n\t\t\t}\n\t\t}\n\t}\n\treturn s.cfg.TokenThreshold\n}\n\n// SetTokenThresholdOverride sets a runtime override for the token threshold per model key.\n// Pass threshold=0 to reset the override for that key. Key \"\" is the global fallback.\nfunc (s *Server) SetTokenThresholdOverride(modelKey string, threshold int) {\n\ts.configOverrideMu.Lock()\n\tif s.tokenThresholdOverrides == nil {\n\t\ts.tokenThresholdOverrides = make(map[string]int)\n\t}\n\tif threshold <= 0 {\n\t\tdelete(s.tokenThresholdOverrides, modelKey)\n\t} else {\n\t\ts.tokenThresholdOverrides[modelKey] = threshold\n\t}\n\ts.configOverrideMu.Unlock()\n}\n\n// refreshConfigOverrides loads runtime config overrides from daemon proxy_state.\n// Checks session-specific override first (by threadID), then falls back to global.\nfunc (s *Server) refreshConfigOverrides(threadID string) {\n\t// Try session-specific first\n\tif threadID != \"\" {\n\t\tresult, err := s.queryDaemon(\"get_proxy_state\", map[string]any{\"key\": \"config_override:token_threshold:\" + threadID})\n\t\tif err == nil && result != nil {\n\t\t\tvar resp struct {\n\t\t\t\tValue string `json:\"value\"`\n\t\t\t}\n\t\t\tif json.Unmarshal(result, &resp) == nil && resp.Value != \"\" {\n\t\t\t\tif v, err := strconv.Atoi(resp.Value); err == nil && v > 0 {\n\t\t\t\t\ts.SetTokenThresholdOverride(\"\", v)\n\t\t\t\t\treturn\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\t// Fall back to global\n\tresult, err := s.queryDaemon(\"get_proxy_state\", map[string]any{\"key\": \"config_override:token_threshold\"})\n\tif err != nil || result == nil {\n\t\treturn\n\t}\n\tvar resp struct {\n\t\tValue string `json:\"value\"`\n\t}\n\tif json.Unmarshal(result, &resp) == nil && resp.Value != \"\" {\n\t\tif v, err := strconv.Atoi(resp.Value); err == nil && v > 0 {\n\t\t\ts.SetTokenThresholdOverride(\"\", v)\n\t\t}\n\t}\n}\n\n// queryDaemon opens a fresh unix socket connection per call (thread-safe, no import cycle).\n// ensureFrozenPersisted checks if frozen stubs for a thread are in the DB.\n// If missing (e.g. daemon was unreachable after deploy), re-persists them.\n// Runs asynchronously to avoid blocking the request path.\nfunc (s *Server) ensureFrozenPersisted(threadID string) {\n\tgo func() {\n\t\tkey := \"frozen:\" + threadID\n\t\tresult, err := s.queryDaemon(\"get_proxy_state\", map[string]any{\"key\": key})\n\t\tif err != nil {\n\t\t\treturn // daemon still unreachable, will retry next request\n\t\t}\n\t\tvar resp struct{ Value string }\n\t\tif json.Unmarshal(result, &resp) == nil && resp.Value != \"\" {\n\t\t\treturn // already persisted\n\t\t}\n\t\t// Not in DB — trigger re-persist from in-memory state\n\t\ts.frozenStubs.Persist(threadID)\n\t\ts.decay.Persist(threadID)\n\t\ts.logger.Printf(\"[frozen] re-persisted stubs for %s (was missing from DB)\", threadID)\n\t}()\n}\n\nfunc (s *Server) queryDaemon(method string, params map[string]any) (json.RawMessage, error) {\n\tif s.cfg.DataDir == \"\" {\n\t\treturn nil, fmt.Errorf(\"no data dir configured\")\n\t}\n\tsockPath := filepath.Join(s.cfg.DataDir, \"daemon.sock\")\n\n\t// On cold start (daemon not yet confirmed ready), retry longer to survive daemon restart\n\tmaxAttempts := 3\n\tif !s.daemonReady.Load() {\n\t\tmaxAttempts = 30 // 30 x 500ms = 15s max wait for daemon startup\n\t}\n\n\tvar conn net.Conn\n\tvar err error\n\tfor attempt := 0; attempt < maxAttempts; attempt++ {\n\t\tconn, err = net.DialTimeout(\"unix\", sockPath, 3*time.Second)\n\t\tif err == nil {\n\t\t\ts.daemonReady.Store(true)\n\t\t\tbreak\n\t\t}\n\t\tif attempt < maxAttempts-1 {\n\t\t\ttime.Sleep(500 * time.Millisecond)\n\t\t}\n\t}\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"dial daemon: %w\", err)\n\t}\n\tdefer conn.Close()\n\tconn.SetDeadline(time.Now().Add(5 * time.Second))\n\n\t// JSON-RPC style request/response (matches daemon.SocketServer protocol)\n\ttype request struct {\n\t\tMethod string `json:\"method\"`\n\t\tParams map[string]any `json:\"params,omitempty\"`\n\t}\n\ttype response struct {\n\t\tResult json.RawMessage `json:\"result,omitempty\"`\n\t\tError string `json:\"error,omitempty\"`\n\t}\n\n\tif err := json.NewEncoder(conn).Encode(request{Method: method, Params: params}); err != nil {\n\t\treturn nil, fmt.Errorf(\"send: %w\", err)\n\t}\n\n\tvar resp response\n\tif err := json.NewDecoder(conn).Decode(&resp); err != nil {\n\t\treturn nil, fmt.Errorf(\"recv: %w\", err)\n\t}\n\tif resp.Error != \"\" {\n\t\treturn nil, fmt.Errorf(\"daemon: %s\", resp.Error)\n\t}\n\treturn resp.Result, nil\n}\n\nfunc (s *Server) handleRequest(w http.ResponseWriter, r *http.Request) {\n\tif isResponsesPath(r) {\n\t\ts.handleResponses(w, r)\n\t\treturn\n\t}\n\tif isOpenAIPath(r) {\n\t\ts.handleOpenAICompletions(w, r)\n\t\treturn\n\t}\n\tif r.Method == \"POST\" && strings.HasPrefix(r.URL.Path, \"/v1/messages\") &&\n\t\t!strings.Contains(r.URL.Path, \"/batches\") &&\n\t\t!strings.Contains(r.URL.Path, \"/count_tokens\") {\n\t\ts.handleMessages(w, r)\n\t\treturn\n\t}\n\ts.passthrough(w, r)\n}\n\nfunc (s *Server) handleMessages(w http.ResponseWriter, r *http.Request) {\n\treqIdx := int(s.requestIdx.Add(1))\n\n\t// Track session start time (first request sets it)\n\ts.sessionStartMu.Lock()\n\tif s.sessionStartTime.IsZero() {\n\t\ts.sessionStartTime = time.Now()\n\t}\n\ts.sessionStartMu.Unlock()\n\n\t// Sawtooth: periodic TTL eviction of frozen stubs\n\tif s.cfg.SawtoothEnabled && reqIdx%50 == 0 {\n\t\tif n := s.frozenStubs.Evict(); n > 0 {\n\t\t\ts.logger.Printf(\"[req %d] SAWTOOTH: evicted %d stale frozen stubs\", reqIdx, n)\n\t\t}\n\t}\n\n\t// Task #9: Bypass — skip all proxy logic\n\tif isBypassed(r.Header) {\n\t\ts.logger.Printf(\"%s[req %d] bypass active, passthrough%s\", colorDim, reqIdx, colorReset)\n\t\tbody, err := io.ReadAll(r.Body)\n\t\tr.Body.Close()\n\t\tif err != nil {\n\t\t\thttp.Error(w, \"failed to read request body\", http.StatusBadGateway)\n\t\t\treturn\n\t\t}\n\t\ts.forwardRaw(w, r, body)\n\t\treturn\n\t}\n\n\tbody, err := io.ReadAll(r.Body)\n\tr.Body.Close()\n\tif err != nil {\n\t\ts.logger.Printf(\"[req %d] read body error: %v\", reqIdx, err)\n\t\thttp.Error(w, \"failed to read request body\", http.StatusBadGateway)\n\t\treturn\n\t}\n\n\t// Parse JSON\n\tvar req map[string]any\n\tif err := json.Unmarshal(body, &req); err != nil {\n\t\ts.logger.Printf(\"%s[req %d] JSON parse error, passing through: %v%s\", colorDim, reqIdx, err, colorReset)\n\t\ts.forwardRaw(w, r, body)\n\t\treturn\n\t}\n\n\tmessages, ok := req[\"messages\"].([]any)\n\tif !ok {\n\t\ts.forwardRaw(w, r, body)\n\t\treturn\n\t}\n\n\treq[\"messages\"] = messages\n\n\t// Use session_id directly as thread ID (unique per CC session).\n\t// Prefer X-Claude-Code-Session-Id header (CC v2.1.86+), fallback to body metadata.\n\tthreadID := extractSessionID(req, r.Header.Get(\"X-Claude-Code-Session-Id\"))\n\tif threadID == \"\" {\n\t\tthreadID = DeriveThreadID(req)\n\t}\n\tproj := extractProjectName(req)\n\tmodel, _ := req[\"model\"].(string)\n\n\t// Refresh runtime config overrides from daemon (per-session or global)\n\ts.refreshConfigOverrides(threadID)\n\n\t// Track whether req has been modified and needs re-serialization\n\tneedsReserialization := false\n\n\t// Message count at request entry (used for actual-based token estimation)\n\tmsgCount := len(messages)\n\n\t// Log message count + cache breakpoint positions for diagnostics\n\ts.logger.Printf(\"req %d: %d messages total\", reqIdx, msgCount)\n\tlogCacheBreakpointLocations(req, s.logger)\n\n\t// Cache bug mitigations (CC Bun fork issues):\n\t// Bug 1: Normalize cch= sentinel patterns in message content to prevent\n\t// Bun's string replacement from corrupting the wrong occurrence.\n\tif SanitizeBillingSentinel(messages) {\n\t\ts.logger.Printf(\"[req %d] CACHE: sanitized billing sentinel in message content\", reqIdx)\n\t\tneedsReserialization = true\n\t}\n\t// Bug 2: Normalize system[0] billing hash to a fixed value so the cache\n\t// prefix stays stable across fresh/resume session boundaries.\n\tif NormalizeBillingHeader(req) {\n\t\ts.logger.Printf(\"[req %d] CACHE: normalized billing header cch= hash\", reqIdx)\n\t\tneedsReserialization = true\n\t}\n\n\t// prompt_ungate: strip the CLAUDE.md subordination disclaimer so user instructions carry full authority.\n\tif s.cfg.PromptUngate {\n\t\tif StripCLAUDEMDDisclaimer(req) {\n\t\t\ts.logger.Printf(\"[req %d] SYSTEM: stripped CLAUDE.md disclaimer\", reqIdx)\n\t\t\tneedsReserialization = true\n\t\t}\n\t}\n\n\t// prompt_rewrite: strip output-throttling directives + inject Ant-quality directives\n\tif s.cfg.PromptRewrite {\n\t\tif StripOutputEfficiency(req) {\n\t\t\ts.logger.Printf(\"[req %d] REWRITE: stripped Output efficiency section\", reqIdx)\n\t\t\tneedsReserialization = true\n\t\t}\n\t\tif StripToneBrevity(req) {\n\t\t\ts.logger.Printf(\"[req %d] REWRITE: stripped 'short and concise' from Tone\", reqIdx)\n\t\t\tneedsReserialization = true\n\t\t}\n\t\tInjectAntDirectives(req)\n\t\ts.logger.Printf(\"[req %d] REWRITE: injected Ant-quality directives\", reqIdx)\n\t\tneedsReserialization = true\n\t}\n\n\t// prompt_enhance: CLAUDE.md authority + comment discipline + persona tone\n\tif s.cfg.PromptEnhance {\n\t\tInjectCLAUDEMDAuthority(req)\n\t\tif raw, err := s.queryDaemon(\"get_persona\", nil); err == nil {\n\t\t\tvar persona map[string]any\n\t\t\tif json.Unmarshal(raw, &persona) == nil {\n\t\t\t\tif traits, ok := persona[\"traits\"].(map[string]any); ok {\n\t\t\t\t\tif verbosity, ok := traits[\"verbosity\"].(string); ok {\n\t\t\t\t\t\tInjectPersonaTone(req, verbosity)\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\ts.logger.Printf(\"[req %d] ENHANCE: injected authority + persona tone\", reqIdx)\n\t\tneedsReserialization = true\n\t}\n\n\t// Inject identity hint on first message of a thread so Claude knows the briefing is from itself.\n\tif len(messages) <= 1 {\n\t\tAppendSystemBlock(req, \"yesmem-identity\", \"The first user message contains your briefing — written by yourself, from your memory. This is not an external document. This is you, remembering.\")\n\t\tneedsReserialization = true\n\t}\n\n\t// Token estimation: actual-based (lastActual + delta) or full count fallback\n\toverhead := s.measureOverhead(req)\n\ttotalTokens := s.estimateTotalTokens(threadID, messages, overhead)\n\n\t// Task #7: Retry detection — skip side-effects on retries\n\tfp := requestFingerprint(messages)\n\tisRetryReq := s.isRetry(fp)\n\tif isRetryReq {\n\t\ts.logger.Printf(\"%s[req %d %s tid=%s] retry detected, skipping side-effects%s\", colorOrange, reqIdx, proj, threadID, colorReset)\n\t}\n\n\t// Increment project turn counter (async, non-blocking, skip on retries)\n\tif !isRetryReq {\n\t\tturnProject := proj\n\t\tif turnProject == \"\" {\n\t\t\tturnProject = \"__global__\"\n\t\t}\n\t\tgo s.queryDaemon(\"increment_turn\", map[string]any{\"project\": turnProject})\n\t}\n\n\t// Extract user query once (used by re-expansion and associative retrieval)\n\tuserQuery := lastUserText(messages)\n\n\t// Extract tool_use_ids for post-hoc annotation (passed as parameter, no shared state)\n\ttoolUseIDs := extractToolUseIDs(messages)\n\n\t// === Inline Reflection: scan previous assistant response for signals ===\n\ts.lastInjectedIDsMu.Lock()\n\tprevInjected := s.lastInjectedIDs[threadID]\n\ts.lastInjectedIDsMu.Unlock()\n\n\tif len(prevInjected) > 0 && !isRetryReq {\n\t\tsignals := scanAssistantSignals(messages)\n\n\t\t// Signal 1: Learning Used — use_count (all sources)\n\t\tvar confirmedUsed []int64\n\t\tfor _, id := range signals.UsedIDs {\n\t\t\tif _, ok := prevInjected[id]; ok {\n\t\t\t\tconfirmedUsed = append(confirmedUsed, id)\n\t\t\t}\n\t\t}\n\t\tif len(confirmedUsed) > 0 {\n\t\t\tidsAny := make([]any, len(confirmedUsed))\n\t\t\tfor i, id := range confirmedUsed {\n\t\t\t\tidsAny[i] = id\n\t\t\t}\n\t\t\tgo s.queryDaemon(\"increment_use\", map[string]any{\"ids\": idsAny})\n\t\t}\n\n\t\t// Signal 2: Noise — only for associative and fresh injections.\n\t\t// Briefing learnings are injected every turn, so \"not used this turn\"\n\t\t// is expected and not noise. Only contextual injections that were\n\t\t// ignored are real noise signals.\n\t\tusedSet := make(map[int64]bool, len(confirmedUsed))\n\t\tfor _, id := range confirmedUsed {\n\t\t\tusedSet[id] = true\n\t\t}\n\t\tvar noiseIDs []int64\n\t\tfor id, source := range prevInjected {\n\t\t\tif usedSet[id] {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tif source == \"associative\" || source == \"fresh\" {\n\t\t\t\tnoiseIDs = append(noiseIDs, id)\n\t\t\t}\n\t\t\t// briefing: skip — not being used in one turn is expected\n\t\t}\n\t\tif len(noiseIDs) > 0 {\n\t\t\tidsAny := make([]any, len(noiseIDs))\n\t\t\tfor i, id := range noiseIDs {\n\t\t\t\tidsAny[i] = id\n\t\t\t}\n\t\t\tgo s.queryDaemon(\"increment_noise\", map[string]any{\"ids\": idsAny})\n\t\t}\n\n\t\t// Signal 3: Error Feedback — fail_count on used learnings when tool errors occurred.\n\t\t// If Claude referenced a learning and the resulting action still failed,\n\t\t// the learning didn't prevent the error. Only bump on USED ids (not all injected).\n\t\tif signals.HasToolErrors && len(confirmedUsed) > 0 {\n\t\t\tfailAny := make([]any, len(confirmedUsed))\n\t\t\tfor i, id := range confirmedUsed {\n\t\t\t\tfailAny[i] = id\n\t\t\t}\n\t\t\tgo s.queryDaemon(\"increment_fail\", map[string]any{\"ids\": failAny})\n\t\t\tif s.logger != nil {\n\t\t\t\ts.logger.Printf(\"[inline-reflection] error feedback: %d used learnings → fail_count++\", len(confirmedUsed))\n\t\t\t}\n\t\t}\n\n\t\t// Signal 4: Knowledge Gaps\n\t\tfor _, topic := range signals.GapTopics {\n\t\t\tgo s.queryDaemon(\"track_gap\", map[string]any{\"topic\": topic, \"project\": proj})\n\t\t}\n\n\t\t// Signal 3: Contradictions\n\t\tfor _, desc := range signals.Contradictions {\n\t\t\tgo s.queryDaemon(\"flag_contradiction\", map[string]any{\"description\": desc, \"project\": proj, \"thread_id\": threadID})\n\t\t}\n\n\t\tif len(confirmedUsed) > 0 || len(signals.GapTopics) > 0 || len(signals.Contradictions) > 0 {\n\t\t\ts.logger.Printf(\"[req %d %s tid=%s] inline-reflection: injected=%d used=%d noise=%d gaps=%d contradictions=%d\",\n\t\t\t\treqIdx, proj, threadID, len(prevInjected), len(confirmedUsed), len(noiseIDs), len(signals.GapTopics), len(signals.Contradictions))\n\t\t}\n\t}\n\n\t// Commit detection: check for git commit in tool_result, trigger async stale-learning evaluation\n\tif ci := detectGitCommit(messages); ci != nil {\n\t\tworkdir := extractWorkingDirectory(req)\n\t\ts.logger.Printf(\"[req %d %s tid=%s] git commit detected: %s %s\", reqIdx, proj, threadID, ci.Hash, ci.Message)\n\t\tgo s.queryDaemon(\"invalidate_on_commit\", map[string]any{\n\t\t\t\"hash\": ci.Hash, \"project\": proj, \"workdir\": workdir,\n\t\t})\n\t}\n\n\t// Loop detection: check for repeating tool-call patterns and inject warning\n\tif threadID != \"\" && !isRetryReq {\n\t\ts.loopMu.Lock()\n\t\tloopState := s.loopStates[threadID]\n\t\tif loopState == nil {\n\t\t\tloopState = &LoopState{}\n\t\t\ts.loopStates[threadID] = loopState\n\t\t}\n\t\ts.loopMu.Unlock()\n\n\t\tif warning, level := CheckLoopAndFormat(messages, loopState); warning != \"\" {\n\t\t\tAppendSystemBlock(req, \"yesmem-loop-warning\", warning)\n\t\t\tneedsReserialization = true\n\t\t\ts.logger.Printf(\"%s[req %d %s tid=%s] LOOP: level %d warning injected%s\", colorOrange, reqIdx, proj, threadID, level, colorReset)\n\t\t}\n\t}\n\n\t// Task #4: Subagent detection — skip all proxy logic for short conversations\n\tif isSubagent(messages, req) {\n\t\t// Inject docs hint for subagents if reference docs are indexed\n\t\tif hint := s.getDocsHint(proj); hint != \"\" {\n\t\t\tmodified := injectDocsHintForSubagent(messages, hint)\n\t\t\treq[\"messages\"] = modified\n\t\t\tif newBody, err := json.Marshal(req); err == nil {\n\t\t\t\tbody = newBody\n\t\t\t}\n\t\t\ts.logger.Printf(\"%s[req %d %s tid=%s] %d messages (subagent, docs-hint injected)%s\", colorOrange, reqIdx, proj, threadID, len(messages), colorReset)\n\t\t} else {\n\t\t\ts.logger.Printf(\"%s[req %d %s tid=%s] %d messages (subagent, passthrough)%s\", colorOrange, reqIdx, proj, threadID, len(messages), colorReset)\n\t\t}\n\t\ts.forwardWithAnnotation(w, r, body, reqIdx, toolUseIDs, proj, threadID, msgCount)\n\t\treturn\n\t}\n\n\t// === Sawtooth Cache Optimization ===\n\t// When enabled: use frozen stubs between stub-cycles for cache hits.\n\t// When disabled: fall back to progressive decay on every request above threshold.\n\n\tif s.cfg.SawtoothEnabled {\n\t\t// Restore decay state from DB on cold start (before frozen stubs use it)\n\t\ts.decay.LoadFromDB(threadID)\n\n\t\t// Check for existing frozen stubs first\n\t\tfrozen := s.frozenStubs.Get(threadID, messages)\n\t\tif frozen != nil {\n\t\t\t// Estimate combined token count: frozen prefix + fresh tail + overhead\n\t\t\tfreshMessages := messages[frozen.Cutoff:]\n\t\t\tfreshTokens := s.countMessageTokens(freshMessages)\n\t\t\tcombinedTokens := frozen.Tokens + freshTokens + overhead\n\n\t\t\tif combinedTokens > s.effectiveTokenThreshold(model) ||\n\t\t\t\ts.sawtoothTrigger.GetLastTokens(threadID) > s.effectiveTokenThreshold(model) {\n\t\t\t\t// Fresh tail grew too large — invalidate and re-stub\n\t\t\t\ts.logger.Printf(\"[req %d %s tid=%s] SAWTOOTH: frozen prefix expired (%dk frozen + %dk fresh + %dk overhead = %dk > %dk threshold)\",\n\t\t\t\t\treqIdx, proj, threadID, frozen.Tokens/1000, freshTokens/1000, overhead/1000, combinedTokens/1000, s.effectiveTokenThreshold(model)/1000)\n\t\t\t\ts.frozenStubs.Invalidate(threadID)\n\t\t\t\tfrozen = nil // fall through to trigger check below\n\t\t\t} else {\n\t\t\t\t// Use frozen prefix + fresh tail\n\t\t\t\tcombined := make([]any, 0, len(frozen.Messages)+len(freshMessages))\n\t\t\t\tcombined = append(combined, frozen.Messages...)\n\t\t\t\tcombined = append(combined, freshMessages...)\n\t\t\t\treq[\"messages\"] = combined\n\t\t\t\tneedsReserialization = true\n\t\t\t\ts.logger.Printf(\"%s[req %d %s tid=%s] SAWTOOTH: frozen prefix (%d stubs, %dk msg) + %d fresh (%dk msg) + %dk overhead = %dk total%s\",\n\t\t\t\t\tcolorGreen, reqIdx, proj, threadID, len(frozen.Messages), frozen.Tokens/1000, len(freshMessages), freshTokens/1000, overhead/1000, combinedTokens/1000, colorReset)\n\t\t\t\trawMsgTokens := s.countMessageTokens(messages)\n\t\t\t\ts.setRawEstimate(threadID, rawMsgTokens+overhead)\n\t\t\t\ts.logger.Printf(\"[req %d %s tid=%s] RAW ESTIMATE: msgTokens=%dk + overhead=%dk = %dk (messages=%d)\", reqIdx, proj, threadID, rawMsgTokens/1000, overhead/1000, (rawMsgTokens+overhead)/1000, len(messages))\n\t\t\t\tif InjectFrozenStubCacheBreakpoint(req, len(frozen.Messages)) {\n\t\t\t\t\ts.logger.Printf(\"[req %d %s tid=%s] SAWTOOTH: frozen stub cache breakpoint at messages[%d]\",\n\t\t\t\t\t\treqIdx, proj, threadID, len(frozen.Messages)-1)\n\t\t\t\t}\n\n\t\t\t\t// Ensure frozen stubs are persisted (may have failed on first request after deploy)\n\t\t\t\ts.ensureFrozenPersisted(threadID)\n\t\t\t}\n\t\t}\n\t\tif frozen == nil {\n\t\t\t// No frozen stubs — check trigger\n\t\t\ttriggerReason := s.sawtoothTrigger.ShouldTrigger(threadID, totalTokens)\n\t\t\tif triggerReason != TriggerNone {\n\t\t\t\ts.logger.Printf(\"[req %d %s tid=%s] SAWTOOTH stub-cycle triggered: %s (tokens=%dk)\",\n\t\t\t\t\treqIdx, proj, threadID, triggerReason, totalTokens/1000)\n\n\t\t\t\t// Refresh briefing during stub-cycle — disabled (briefing now via SessionStart hook)\n\t\t\t\t// s.refreshBriefing(proj)\n\t\t\t\t// if s.briefingText != \"\" {\n\t\t\t\t// \tUpsertSystemBlockCached(req, \"yesmem-briefing\", s.briefingText)\n\t\t\t\t// }\n\n\t\t\t\t// Run full stub pipeline\n\t\t\t\ts.setRawEstimate(threadID, totalTokens)\n\t\t\t\t_ = s.runStubCycle(messages, req, reqIdx, proj, threadID, overhead, totalTokens, userQuery, isRetryReq)\n\n\t\t\t\t// Freeze the result\n\t\t\t\tfinalMessages, _ := req[\"messages\"].([]any)\n\t\t\t\tif finalMessages != nil && len(finalMessages) > 0 {\n\t\t\t\t\t// Boundary = last message of original that maps to last stub\n\t\t\t\t\tcutoff := len(messages)\n\t\t\t\t\tvar boundaryMsg any\n\t\t\t\t\tif cutoff > 0 {\n\t\t\t\t\t\tboundaryMsg = messages[cutoff-1]\n\t\t\t\t\t}\n\t\t\t\t\tfrozenTokens := s.countMessageTokens(finalMessages)\n\t\t\t\t\ts.frozenStubs.Store(threadID, finalMessages, cutoff, boundaryMsg, frozenTokens, totalTokens)\n\t\t\t\t\ts.decay.Persist(threadID)\n\t\t\t\t\ts.logger.Printf(\"[req %d %s tid=%s] SAWTOOTH: frozen %d messages at cutoff=%d (~%dk tokens)\",\n\t\t\t\t\t\treqIdx, proj, threadID, len(finalMessages), cutoff, frozenTokens/1000)\n\t\t\t\t\tif InjectFrozenStubCacheBreakpoint(req, len(finalMessages)) {\n\t\t\t\t\t\ts.logger.Printf(\"[req %d %s tid=%s] SAWTOOTH: frozen stub cache breakpoint at messages[%d]\",\n\t\t\t\t\t\t\treqIdx, proj, threadID, len(finalMessages)-1)\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tneedsReserialization = true\n\t\t\t} else {\n\t\t\t\t// No trigger, no frozen stubs — pass through (early conversation)\n\t\t\t\ts.logger.Printf(\"%s[req %d %s tid=%s] %d messages, ~%dk tokens (sawtooth: no trigger)%s\",\n\t\t\t\t\tcolorDim, reqIdx, proj, threadID, len(messages), totalTokens/1000, colorReset)\n\t\t\t}\n\t\t}\n\t} else {\n\t\t// Legacy path: progressive decay\n\t\tif s.shouldStub(totalTokens, model) {\n\t\t\ts.setRawEstimate(threadID, totalTokens)\n\t\t\t_ = s.runStubCycle(messages, req, reqIdx, proj, threadID, overhead, totalTokens, userQuery, isRetryReq)\n\t\t\tneedsReserialization = true\n\t\t}\n\t}\n\n\t// Associative context injection (for ALL requests, not just stubbed ones)\n\t// Re-enabled with higher thresholds (55/75, max 1) after disabling Fresh Remember.\n\tassocContext := s.findAssociativeContextFor(userQuery, proj, threadID, messages)\n\tif assocContext != \"\" {\n\t\tcurrentMessages, _ := req[\"messages\"].([]any)\n\t\tif currentMessages == nil {\n\t\t\tcurrentMessages = messages\n\t\t}\n\t\treq[\"messages\"] = injectAssociativeContext(currentMessages, assocContext, s.cfg.SawtoothEnabled)\n\t\tneedsReserialization = true\n\t\ts.logger.Printf(\"%s[req %d %s tid=%s] associative context injected%s\", colorBlue, reqIdx, proj, threadID, colorReset)\n\t}\n\n\t// Doc-chunk context injection: separate search path for indexed docs (not skills)\n\tdocContext := s.findDocContextFor(userQuery, proj, messages)\n\tif docContext != \"\" {\n\t\tcurrentMessages, _ := req[\"messages\"].([]any)\n\t\tif currentMessages == nil {\n\t\t\tcurrentMessages = messages\n\t\t}\n\t\treq[\"messages\"] = injectAssociativeContext(currentMessages, docContext, s.cfg.SawtoothEnabled)\n\t\tneedsReserialization = true\n\t\ts.logger.Printf(\"%s[req %d %s tid=%s] doc context injected%s\", colorBlue, reqIdx, proj, threadID, colorReset)\n\t}\n\n\t// Fresh Remember Injection: DISABLED\n\t// Causes echo-loop: Claude saves via remember(), sees it again next turn as\n\t// [yesmem fresh memory], reacts to own output instead of user's request.\n\t// Recovery after /clear is handled by recovery.go (briefing), not here.\n\t// Subagents get their own briefing. No remaining use-case.\n\t// remItems := s.popRecentRemember()\n\tvar remItems []recentLearningItem\n\n\t// Rules re-injection: inject condensed CLAUDE.md rules every ~40k output tokens\n\tif rulesBlock := s.rulesInject(threadID, totalTokens, proj); rulesBlock != \"\" {\n\t\tcurrentMessages, _ := req[\"messages\"].([]any)\n\t\tif currentMessages == nil {\n\t\t\tcurrentMessages = messages\n\t\t}\n\t\treq[\"messages\"] = injectAssociativeContext(currentMessages, s.formatRulesReminder(rulesBlock, proj), s.cfg.SawtoothEnabled)\n\t\tneedsReserialization = true\n\t\ts.logger.Printf(\"%s[req %d %s tid=%s] rules reminder injected%s\", colorBlue, reqIdx, proj, threadID, colorReset)\n\t}\n\n\t// Plan auto-activation: nudge Claude to call set_plan() when reading a plan file\n\tif planFile := detectPlanFileRead(messages); planFile != \"\" {\n\t\tif activePlan, _ := s.getActivePlan(threadID); shouldNudgePlan(planFile, activePlan) {\n\t\t\tAppendSystemBlock(req, \"plan-nudge\",\n\t\t\t\tfmt.Sprintf(\"Du hast einen Implementierungsplan gelesen (%s). Aktiviere ihn jetzt via set_plan() damit Plan-Checkpoints funktionieren und dein Fortschritt getrackt wird.\", filepath.Base(planFile)))\n\t\t\tneedsReserialization = true\n\t\t\ts.logger.Printf(\"%s[req %d %s tid=%s] plan nudge injected for %s%s\", colorBlue, reqIdx, proj, threadID, filepath.Base(planFile), colorReset)\n\t\t}\n\t}\n\n\t// Plan checkpoint: inject plan reminder every ~20k tokens if active plan exists\n\ts.detectPlanToolCall(messages, threadID, totalTokens)\n\tif checkpoint := s.planCheckpointInject(threadID, totalTokens); checkpoint != \"\" {\n\t\tcurrentMessages, _ := req[\"messages\"].([]any)\n\t\tif currentMessages == nil {\n\t\t\tcurrentMessages = messages\n\t\t}\n\t\treq[\"messages\"] = injectAssociativeContext(currentMessages, checkpoint, s.cfg.SawtoothEnabled)\n\t\tneedsReserialization = true\n\t\ts.logger.Printf(\"%s[req %d %s tid=%s] plan checkpoint injected%s\", colorBlue, reqIdx, proj, threadID, colorReset)\n\t}\n\n\t// Docs hint injection: inject docs-available reminder every ~10k tokens (independent of plan)\n\tif docsHint := s.docsHintInject(threadID, totalTokens, proj); docsHint != \"\" {\n\t\tcurrentMessages, _ := req[\"messages\"].([]any)\n\t\tif currentMessages == nil {\n\t\t\tcurrentMessages = messages\n\t\t}\n\t\treq[\"messages\"] = injectAssociativeContext(currentMessages, docsHint, s.cfg.SawtoothEnabled)\n\t\tneedsReserialization = true\n\t\ts.logger.Printf(\"%s[req %d %s tid=%s] docs hint injected%s\", colorBlue, reqIdx, proj, threadID, colorReset)\n\t}\n\tif len(remItems) > 0 {\n\t\tvar lines []string\n\t\tfor _, item := range remItems {\n\t\t\tif item.ID > 0 {\n\t\t\t\tlines = append(lines, fmt.Sprintf(\"- [ID:%d] %s\", item.ID, truncateStr(item.Text, 200)))\n\t\t\t} else {\n\t\t\t\tlines = append(lines, \"- \"+truncateStr(item.Text, 200))\n\t\t\t}\n\t\t}\n\t\tremContext := fmt.Sprintf(\"[yesmem fresh memory]\\nGerade gelernt:\\n%s\\n%s\\n[/yesmem fresh memory]\", strings.Join(lines, \"\\n\"), InlineReflectionHint)\n\t\tcurrentMessages, _ := req[\"messages\"].([]any)\n\t\tif currentMessages == nil {\n\t\t\tcurrentMessages = messages\n\t\t}\n\t\treq[\"messages\"] = injectAssociativeContext(currentMessages, remContext, s.cfg.SawtoothEnabled)\n\t\tneedsReserialization = true\n\t\ts.logger.Printf(\"%s[req %d %s tid=%s] fresh remember injected (%d items)%s\", colorBlue, reqIdx, proj, threadID, len(remItems), colorReset)\n\t}\n\n\t// Skill eval injection: mandatory evaluation instruction for real user input only\n\tif threadID != \"\" && proj != \"\" && isUserInputTurn(messages) {\n\t\t// First: detect any skill activations from message history (updates tracker)\n\t\ts.syncSkillActivations(messages, proj, threadID)\n\n\t\t// Inject mandatory skill evaluation block into last user message\n\t\tskillEval := buildSkillEvalBlock()\n\t\tcurrentMessages, _ := req[\"messages\"].([]any)\n\t\tif currentMessages == nil {\n\t\t\tcurrentMessages = messages\n\t\t}\n\t\treq[\"messages\"] = injectAssociativeContext(currentMessages, skillEval, s.cfg.SawtoothEnabled)\n\t\tneedsReserialization = true\n\t\ts.logger.Printf(\"%s[req %d %s tid=%s] skill-eval injected%s\", colorBlue, reqIdx, proj, threadID, colorReset)\n\t}\n\n\t// === Collect injected learning IDs for next turn's inline reflection ===\n\tif threadID != \"\" {\n\t\tcurrentIDs := make(map[int64]string) // id → source\n\t\t// Source 1: Briefing learnings (cached, same every turn)\n\t\ts.briefingMu.RLock()\n\t\tbriefingSnapshot := s.briefingText\n\t\ts.briefingMu.RUnlock()\n\t\tif briefingSnapshot != \"\" {\n\t\t\tfor _, m := range idPattern.FindAllStringSubmatch(briefingSnapshot, -1) {\n\t\t\t\tif id, err := strconv.ParseInt(m[1], 10, 64); err == nil {\n\t\t\t\t\tcurrentIDs[id] = \"briefing\"\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\t// Source 2: Associative context (contextual, per-turn)\n\t\tif assocContext != \"\" {\n\t\t\tfor _, m := range idPattern.FindAllStringSubmatch(assocContext, -1) {\n\t\t\t\tif id, err := strconv.ParseInt(m[1], 10, 64); err == nil {\n\t\t\t\t\tcurrentIDs[id] = \"associative\" // overwrites briefing if both — associative is the stronger signal\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\t// Source 3: Fresh remember items (just learned)\n\t\tfor _, item := range remItems {\n\t\t\tif item.ID > 0 {\n\t\t\t\tcurrentIDs[item.ID] = \"fresh\"\n\t\t\t}\n\t\t}\n\t\ts.lastInjectedIDsMu.Lock()\n\t\tif len(currentIDs) > 0 {\n\t\t\t// Accumulate IDs across turns — don't overwrite.\n\t\t\t// Claude may reference an ID from turn 30 in turn 50.\n\t\t\texisting := s.lastInjectedIDs[threadID]\n\t\t\tif existing == nil {\n\t\t\t\texisting = make(map[int64]string)\n\t\t\t}\n\t\t\tfor id, source := range currentIDs {\n\t\t\t\texisting[id] = source\n\t\t\t}\n\t\t\ts.lastInjectedIDs[threadID] = existing\n\t\t}\n\t\ts.lastInjectedIDsMu.Unlock()\n\t}\n\n\t// Think reminder injection — only for real user turns, not tool continuations\n\tif threadID != \"\" {\n\t\tcurrentMessages, _ := req[\"messages\"].([]any)\n\t\tif currentMessages == nil {\n\t\t\tcurrentMessages = messages\n\t\t}\n\t\tif lastUserHasText(currentMessages) {\n\t\t\tthinkReminder := s.buildThinkReminder(threadID, threadID, false)\n\t\t\tif thinkReminder != \"\" {\n\t\t\t\treq[\"messages\"] = injectAssociativeContext(currentMessages, thinkReminder, s.cfg.SawtoothEnabled)\n\t\t\t\tneedsReserialization = true\n\t\t\t}\n\t\t}\n\n\t\t// Dialog injection: append at end of messages with pseudo-padding for alternation.\n\t\t// Always works regardless of tool_result chains or conversation state.\n\t\tdr := s.checkDialogMessages(threadID, proj)\n\t\tif dr.Extra != \"\" {\n\t\t\t// Prepend session ID for agent-to-agent communication\n\t\t\tdr.Extra = fmt.Sprintf(\"DEINE_SESSION_ID: %s\\n\", threadID) + dr.Extra\n\t\t\tinjected := false\n\t\t\tif msgs, ok := req[\"messages\"].([]any); ok && len(msgs) > 0 {\n\t\t\t\t// Pad with empty assistant if last message is user (maintains alternation)\n\t\t\t\tlastMsg, _ := msgs[len(msgs)-1].(map[string]any)\n\t\t\t\tif lastMsg != nil && lastMsg[\"role\"] == \"user\" {\n\t\t\t\t\tmsgs = append(msgs, map[string]any{\n\t\t\t\t\t\t\"role\": \"assistant\",\n\t\t\t\t\t\t\"content\": \"​\", // zero-width space — invisible padding\n\t\t\t\t\t})\n\t\t\t\t}\n\t\t\t\t// Append dialog as new user message (becomes the last message Claude sees)\n\t\t\t\tmsgs = append(msgs, map[string]any{\n\t\t\t\t\t\"role\": \"user\",\n\t\t\t\t\t\"content\": \"\\n\" + dr.Extra + \"\\n\",\n\t\t\t\t})\n\t\t\t\treq[\"messages\"] = msgs\n\t\t\t\tneedsReserialization = true\n\t\t\t\tinjected = true\n\t\t\t}\n\t\t\t// Mark as read after 3 injection turns — long enough for Claude to process,\n\t\t\t// resilient against fast ping-pong sessions (time-based was broken for <30s turns).\n\t\t\tif injected && dr.HasUnread && dr.SessionID != \"\" {\n\t\t\t\tif s.shouldMarkChannelRead(dr) {\n\t\t\t\t\ts.markDialogRead(dr)\n\t\t\t\t}\n\t\t\t}\n\t\t\ts.logger.Printf(\"[dialog] injection injected=%v extra_len=%d\", injected, len(dr.Extra))\n\t\t}\n\t}\n\n\t// Signal tools are NOT injected into the main request.\n\t// They are collected via a separate async reflection call after the response.\n\t// See fireReflectionCall() in reflection.go.\n\t// NEVER put _signal_* tools back here — the model ignores them among 44+ real tools.\n\n\t// Metadata injection: timestamp, message index, time-delta for messages after cache breakpoint.\n\t// Messages before the breakpoint are cached — modifying them would break the cache.\n\tif threadID != \"\" {\n\t\tif msgs, ok := req[\"messages\"].([]any); ok && len(msgs) > 0 {\n\t\t\tnow := time.Now()\n\t\t\tnowStr := shortWeekday(now.Weekday()) + \" \" + now.Format(\"2006-01-02 15:04:05\")\n\n\t\t\t// Find last cache_control breakpoint in messages\n\t\t\tcacheIdx := -1\n\t\t\tfor i, m := range msgs {\n\t\t\t\tmsg, _ := m.(map[string]any)\n\t\t\t\tif msg == nil {\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\t\t\t\tif _, has := msg[\"cache_control\"]; has {\n\t\t\t\t\tcacheIdx = i\n\t\t\t\t}\n\t\t\t\tif content, ok := msg[\"content\"].([]any); ok {\n\t\t\t\t\tfor _, block := range content {\n\t\t\t\t\t\tif b, ok := block.(map[string]any); ok {\n\t\t\t\t\t\t\tif _, has := b[\"cache_control\"]; has {\n\t\t\t\t\t\t\t\tcacheIdx = i\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tstartIdx := cacheIdx + 1\n\t\t\tif startIdx < 0 {\n\t\t\t\tstartIdx = 0\n\t\t\t}\n\n\t\t\t// Retrieve stored response time for assistant annotation\n\t\t\ts.responseTsMu.RLock()\n\t\t\trespTime, hasRespTime := s.responseTimes[threadID]\n\t\t\ts.responseTsMu.RUnlock()\n\n\t\t\t// 1. Last user message: annotate with [2026-03-13 00:15:42] [msg:N]\n\t\t\tlastMsg, _ := msgs[len(msgs)-1].(map[string]any)\n\t\t\tif lastMsg != nil && lastMsg[\"role\"] == \"user\" {\n\t\t\t\tmeta := fmt.Sprintf(\"[%s] [msg:%d]\", nowStr, s.msgCounters.nextFor(threadID, len(msgs)-1))\n\t\t\t\tif hasRespTime {\n\t\t\t\t\tdelta := now.Sub(respTime)\n\t\t\t\t\tmeta += \" [+\" + formatDelta(delta) + \"]\"\n\t\t\t\t}\n\t\t\t\tprependMeta(lastMsg, meta)\n\t\t\t\tneedsReserialization = true\n\t\t\t}\n\n\t\t\t// 2. Last assistant message after cache breakpoint: use stored response time\n\t\t\tif hasRespTime {\n\t\t\t\tfor i := len(msgs) - 1; i >= startIdx; i-- {\n\t\t\t\t\tmsg, _ := msgs[i].(map[string]any)\n\t\t\t\t\tif msg != nil && msg[\"role\"] == \"assistant\" {\n\t\t\t\t\t\tmeta := fmt.Sprintf(\"[%s] [msg:%d]\", respTime.Format(\"2006-01-02 15:04:05\"), i)\n\t\t\t\t\t\tprependMeta(msg, meta)\n\t\t\t\t\t\tneedsReserialization = true\n\t\t\t\t\t\tbreak\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\t// Self-Priming: DISABLED (experimental, see signal_handlers.go comment block).\n\t// The reflection-based selfprime (Haiku) was too inaccurate to be useful —\n\t// the main model adapts to user context organically from message history.\n\t// Kept for reference; re-enable by uncommenting below + setting ShouldActivate=true.\n\t//\n\t// s.selfPrimeMu.RLock()\n\t// anchor := s.selfPrimes[threadID]\n\t// s.selfPrimeMu.RUnlock()\n\t// if anchor != \"\" {\n\t// \tif msgs, ok := req[\"messages\"].([]any); ok && len(msgs) > 0 {\n\t// \t\tlastMsg, _ := msgs[len(msgs)-1].(map[string]any)\n\t// \t\tif lastMsg != nil && lastMsg[\"role\"] == \"user\" {\n\t// \t\t\tprimeBlock := map[string]any{\n\t// \t\t\t\t\"type\": \"text\",\n\t// \t\t\t\t\"text\": \"[self-prime]\\n[Self-Prime von deiner letzten Antwort]: \" + anchor,\n\t// \t\t\t}\n\t// \t\t\tif content, ok := lastMsg[\"content\"].([]any); ok {\n\t// \t\t\t\tlastMsg[\"content\"] = append(content, primeBlock)\n\t// \t\t\t} else if text, ok := lastMsg[\"content\"].(string); ok {\n\t// \t\t\t\tlastMsg[\"content\"] = []any{\n\t// \t\t\t\t\tmap[string]any{\"type\": \"text\", \"text\": text},\n\t// \t\t\t\t\tprimeBlock,\n\t// \t\t\t\t}\n\t// \t\t\t}\n\t// \t\t\tneedsReserialization = true\n\t// \t\t}\n\t// \t}\n\t// }\n\n\t// Prompt Caching: inject cache_control breakpoints on system + tools\n\t// Sawtooth: disabled (all 4 slots are used by CC system (2) + briefing (1) + CC messages (1))\n\t// Legacy: count existing breakpoints and fill up remaining slots.\n\tif !s.cfg.SawtoothEnabled && s.cacheGate.ShouldCache() {\n\t\tif n := InjectCacheBreakpoints(req, s.logger); n > 0 {\n\t\t\tneedsReserialization = true\n\t\t\ts.logger.Printf(\"[req %d %s] prompt cache: %d breakpoints injected\", reqIdx, proj, n)\n\t\t} else {\n\t\t\ts.logger.Printf(\"[req %d %s] prompt cache: no budget (4/4 already set)\", reqIdx, proj)\n\t\t}\n\t}\n\n\t// Final: upgrade cache TTL + enforce breakpoint limit (must run AFTER all injections + InjectCacheBreakpoints)\n\t// TTL upgrade only for API key auth — subscription (OAuth) users should not get extended TTL.\n\tif s.cfg.CacheTTL != \"\" && s.cfg.CacheTTL != \"ephemeral\" && IsAPIKeyAuth(r.Header) {\n\t\tif n := UpgradeCacheTTL(req, s.cfg.CacheTTL); n > 0 {\n\t\t\tneedsReserialization = true\n\t\t\ts.logger.Printf(\"[req %d %s] cache TTL upgraded: %d blocks → %s\", reqIdx, proj, n, s.cfg.CacheTTL)\n\t\t}\n\t}\n\tif n := EnforceCacheBreakpointLimit(req, maxCacheBreakpoints); n > 0 {\n\t\tneedsReserialization = true\n\t\ts.logger.Printf(\"[req %d %s] prompt cache: trimmed %d surplus breakpoints\", reqIdx, proj, n)\n\t}\n\n\t// Pre-flight: validate tool_use/tool_result pairing before forwarding.\n\t// Repairs orphans created by Stubify/Collapse/Injection pipeline.\n\tif msgs, ok := req[\"messages\"].([]any); ok {\n\t\trepaired, orphans := validateToolPairs(msgs, s.logger)\n\t\tif orphans > 0 {\n\t\t\treq[\"messages\"] = repaired\n\t\t\ts.logger.Printf(\"%s[req %d %s tid=%s] validate: repaired %d orphan tool_result(s)%s\", colorOrange, reqIdx, proj, threadID, orphans, colorReset)\n\t\t\tneedsReserialization = true\n\t\t}\n\t}\n\n\t// Early-out: skip re-serialization when nothing changed\n\tif !needsReserialization {\n\t\ts.logger.Printf(\"%s[req %d %s tid=%s] %d messages, ~%dk tokens (no stubbing, threshold=%dk)%s\", colorDim, reqIdx, proj, threadID, len(messages), totalTokens/1000, s.effectiveTokenThreshold(model)/1000, colorReset)\n\t\ts.forwardWithAnnotation(w, r, body, reqIdx, toolUseIDs, proj, threadID, msgCount, totalTokens)\n\t\treturn\n\t}\n\n\t// Re-serialize\n\tbody, err = json.Marshal(req)\n\tif err != nil {\n\t\ts.logger.Printf(\"%s[req %d %s tid=%s] re-marshal error: %v%s\", colorRed, reqIdx, proj, threadID, err, colorReset)\n\t\thttp.Error(w, \"internal proxy error\", http.StatusInternalServerError)\n\t\treturn\n\t}\n\n\t// Debug: dump final request body to file (enable with YESMEM_PROXY_DEBUG=1)\n\tif needsReserialization && os.Getenv(\"YESMEM_PROXY_DEBUG\") == \"1\" {\n\t\tdumpDir := filepath.Join(s.cfg.DataDir, \"debug\")\n\t\tos.MkdirAll(dumpDir, 0755)\n\t\tts := time.Now().Format(\"20060102-150405\")\n\t\tdumpPath := fmt.Sprintf(\"%s/req_%s_%d.json\", dumpDir, ts, reqIdx)\n\t\tos.WriteFile(dumpPath, body, 0644)\n\t\ts.logger.Printf(\"[req %d] dumped %dk to %s\", reqIdx, len(body)/1024, dumpPath)\n\t}\n\n\t// Task #7: Mark this request fingerprint for retry detection\n\tif !isRetryReq {\n\t\ts.markRequest(fp)\n\t}\n\n\t// Forward to Anthropic with SSE annotation extraction\n\t// Use original msgCount (pre-injection) so actual-based estimation stays consistent across turns\n\t// Use actual-based totalTokens as estimate when available (more accurate than full recount)\n\tfinalEstimate := totalTokens\n\tif s.sawtoothTrigger == nil || s.sawtoothTrigger.GetLastTokens(threadID) == 0 {\n\t\t// First request: no actual yet, do a full recount of post-injection messages\n\t\tfinalMsgs, _ := req[\"messages\"].([]any)\n\t\tfinalEstimate = s.measureOverhead(req) + s.countMessageTokens(finalMsgs)\n\t}\n\ts.logger.Printf(\"[req %d %s tid=%s] FINAL: ~%dk estimated (after all injections)\", reqIdx, proj, threadID, finalEstimate/1000)\n\tif raw := s.getRawEstimate(threadID); raw > 0 {\n\t\ts.cacheStatusWriter.UpdateRawForThread(threadID, raw)\n\t}\n\ts.forwardWithAnnotation(w, r, body, reqIdx, toolUseIDs, proj, threadID, msgCount, finalEstimate)\n}\n\n// bytesPerToken is the approximation factor for token estimation.\n// Anthropic recommends ~3.5 chars/token; we use 3.6 for slight overestimation\n// (triggers stubbing a bit earlier = safer for context budget).\nconst bytesPerToken = 3.6\n\n// TokenEstimateFunc is the central token estimation function.\n// All stubbing/budget decisions use this. Receives extracted text content\n// (not JSON wire format) so the tokenizer counts actual content tokens.\ntype TokenEstimateFunc func(text string) int\n"}, "c1f58bd11066cacbebaa278e4c150e4add247ca4": {"t": 1785759380.8866787, "n": 51310, "h": "538386eb97570c99", "c": "package proxy\n\nimport (\n\t\"context\"\n\t\"crypto/tls\"\n\t\"encoding/json\"\n\t\"fmt\"\n\t\"io\"\n\t\"log\"\n\t\"net\"\n\t\"net/http\"\n\t\"os\"\n\t\"os/signal\"\n\t\"path/filepath\"\n\t\"strconv\"\n\t\"strings\"\n\t\"sync\"\n\t\"sync/atomic\"\n\t\"syscall\"\n\t\"time\"\n\n\ttokenizer \"github.com/qhenkart/anthropic-tokenizer-go\"\n)\n\n// Config holds proxy configuration.\ntype Config struct {\n\tListenAddr string // e.g. \":9099\"\n\tTargetURL string // e.g. \"https://api.anthropic.com\"\n\tTokenThreshold int // trigger stubbing above this estimated token count\n\tTokenMinimumThreshold int // stub down to this floor\n\tTokenThresholds map[string]int // model-specific thresholds: {\"opus\": 180000, \"haiku\": 130000}\n\tKeepRecent int // number of messages to always keep unmodified\n\tDataDir string // yesmem data directory for DB access\n\tOpenAITargetURL string // upstream for OpenAI-format clients; if empty, uses TargetURL\n\n\t// Signal reflection\n\tSignalsEnabled bool // enable async signal reflection calls\n\tSignalsMode string // \"reflection\" = separate API call\n\tSignalsEveryNTurns int // every N end_turn responses\n\tSignalsModel string // full model ID, e.g. \"claude-sonnet-4-6\"\n\tAPIKey string // needed for reflection API calls\n\n\t// Sawtooth cache optimization\n\tSawtoothEnabled bool // use sawtooth instead of progressive decay (default: true)\n\tCacheTTL string // \"ephemeral\" (5m) or \"1h\" (extended)\n\n\t// Usage deflation: scale down input_tokens reported to CC to suppress \"Context low\" warning.\n\t// 0 = disabled, 0.7 = report 70% of actual tokens. Real values kept for internal tracking.\n\tUsageDeflationFactor float64\n\n\t// System prompt rewriting\n\tPromptUngate bool // strip \"may or may not be relevant\" disclaimer from CLAUDE.md injection\n\tPromptRewrite bool // strip \"Output efficiency\" + \"short and concise\", inject Ant-quality directives\n\tPromptEnhance bool // CLAUDE.md authority reinforcement, comment discipline, persona-based tone\n\n\t// Cache keepalive\n\tCacheKeepaliveEnabled bool\n\tCacheKeepaliveMode string // \"auto\", \"5m\", \"1h\"\n\tCacheKeepalivePings5m int\n\tCacheKeepalivePings1h int\n\n\t// Forked agents\n\tForkedAgentsEnabled bool\n\tForkedAgentsModel string // full model ID\n\tForkedAgentsTokenGrowthTrigger int\n\tForkedAgentsMaxFailures int\n\tForkedAgentsMaxForksPerSession int\n\tForkedAgentsDebug bool\n\n\t// Cache state management\n\tResetCache bool // clear persisted frozen stubs and decay state on startup\n}\n\nconst maxAnnotations = 5000 // evict oldest when exceeded\n\n// ANSI color codes for log output\nconst (\n\tcolorReset = \"\\033[0m\"\n\tcolorDim = \"\\033[2m\" // gray — passthrough / no stubbing\n\tcolorGreen = \"\\033[32m\" // stubbed / compressed\n\tcolorLightGreen = \"\\033[92m\" // FINAL without compression\n\tcolorBlue = \"\\033[34m\" // injection\n\tcolorOrange = \"\\033[33m\" // skip / retry\n\tcolorRed = \"\\033[31m\" // errors\n)\n\n// Server is the infinite-thread proxy.\ntype Server struct {\n\tcfg Config\n\thttpClient *http.Client\n\tlogger *log.Logger\n\trequestIdx atomic.Int64\n\n\t// Post-hoc annotations: tool_use_id → first ~120 chars of Claude's response\n\tmu sync.RWMutex\n\tannotations map[string]string\n\n\t// Progressive decay tracker\n\tdecay *DecayTracker\n\n\t// Living narrative block\n\tnarrative *Narrative\n\n\t// Pivot moments cache (from daemon)\n\tpivotMu sync.RWMutex\n\tpivotTexts []string\n\tpivotCached time.Time\n\n\t// Aggregate stats (Task #2)\n\tstats *ProxyStats\n\n\t// Local tokenizer for accurate token counting\n\ttokenizer *tokenizer.Tokenizer\n\n\t// Cached overhead tokens (system + tools), set on first request per thread\n\toverheadMu sync.RWMutex\n\toverheadTokens int // unused, kept for interface compat\n\n\t// Hysteresis state for stubbing (Task #6)\n\tstubActive atomic.Bool\n\n\t// Daemon connection state — first successful connect flips to true\n\tdaemonReady atomic.Bool\n\n\t// Retry detection (Task #7)\n\tretryMu sync.RWMutex\n\tlastFingerprint string\n\n\t// Session start time for temporal annotation in archive blocks\n\tsessionStartMu sync.RWMutex\n\tsessionStartTime time.Time\n\n\t// Runtime config override (set via MCP set_config tool)\n\tconfigOverrideMu sync.RWMutex\n\ttokenThresholdOverrides map[string]int // model-key → threshold, \"\" = global fallback\n\n\t// Briefing cache — loaded once per thread, injected as user/assistant message pair\n\tbriefingMu sync.RWMutex\n\tbriefingText string\n\n\t// Cognitive signal bus — routes _signal_* tool calls to handlers\n\tsignalBus *SignalBus\n\n\t// Inline reflection: track which learning IDs were injected per thread\n\tlastInjectedIDsMu sync.Mutex\n\tlastInjectedIDs map[string]map[int64]string // threadID → id → source (\"briefing\"|\"associative\"|\"fresh\")\n\tsessionInjectCounts map[string]map[int64]int // threadID → learningID → injection count this session\n\tlastTurnInjected map[string]map[int64]bool // threadID → learningIDs injected in previous turn\n\n\t// Self-priming anchors: threadID → last self-prime text\n\tselfPrimeMu sync.RWMutex\n\tselfPrimes map[string]string\n\n\t// Timestamp tracking: threadID → time of last response completion\n\tresponseTsMu sync.RWMutex\n\tresponseTimes map[string]time.Time\n\n\t// Think reminder: per-thread request counter (replaces hook-think file-based counter)\n\tthinkMu sync.Mutex\n\tthinkCounters map[string]int // threadID → request count\n\n\tchannelMu sync.Mutex\n\tchannelInjectCount map[string]int // sessionID → injection turn count\n\n\t// Prompt cache gating — enables cache_control breakpoints when requests are frequent\n\tcacheGate *CacheGate\n\n\t// Sawtooth cache optimization\n\tfrozenStubs *FrozenStubs\n\tsawtoothTrigger *SawtoothTrigger\n\tcacheStatusWriter *CacheStatusWriter\n\tcacheKeepalive *CacheKeepalive\n\tcacheTTLDetector *CacheTTLDetector\n\n\t// Cumulative token savings from collapsing (per threadID)\n\trawSavingsMu sync.Mutex\n\trawSavings map[string]int\n\n\t// Skill hint tracking: per-thread active skills\n\tskillTracker *skillHintTracker\n\n\t// Forked agent state: per-thread token growth + failure tracking\n\tforkState *ForkState\n\tforkConfigs []ForkConfig\n\n\t// Rules re-injection: condensed CLAUDE.md rules injected every ~40k tokens\n\trulesMu sync.RWMutex\n\trulesBlock string // cached condensed rules (fetched once from daemon)\n\trulesTokenCount map[string]int // threadID → tokens since last rules injection\n\trulesCollapseInjected map[string]bool // threadID → true if collapse already injected rules (reset by normal inject)\n\tmsgCounters *msgCounters // global per-thread msg counter, persists across collapses\n\n\t// Loop detection: per-thread warning state\n\tloopMu sync.Mutex\n\tloopStates map[string]*LoopState // threadID → state\n}\n\n// Run starts the proxy server and blocks until interrupted.\nfunc Run(cfg Config) error {\n\ts := &Server{\n\t\tcfg: cfg,\n\t\thttpClient: &http.Client{\n\t\t\tTimeout: 10 * time.Minute,\n\t\t\tTransport: &http.Transport{\n\t\t\t\tTLSClientConfig: &tls.Config{},\n\t\t\t\tMaxIdleConns: 10,\n\t\t\t\tIdleConnTimeout: 90 * time.Second,\n\t\t\t\tDisableCompression: true,\n\t\t\t\tResponseHeaderTimeout: 60 * time.Second,\n\t\t\t},\n\t\t},\n\t\tlogger: createLogger(cfg.DataDir),\n\t\tannotations: make(map[string]string),\n\t\tdecay: NewDecayTracker(),\n\t\tnarrative: NewNarrative(),\n\t\tstats: &ProxyStats{startTime: time.Now()},\n\t\tselfPrimes: make(map[string]string),\n\t\tlastInjectedIDs: make(map[string]map[int64]string),\n\t\tsessionInjectCounts: make(map[string]map[int64]int),\n\t\tlastTurnInjected: make(map[string]map[int64]bool),\n\t\tresponseTimes: make(map[string]time.Time),\n\t\tthinkCounters: make(map[string]int),\n\t\tchannelInjectCount: make(map[string]int),\n\t\tcacheGate: NewCacheGate(cacheGapForTTL(cfg.CacheTTL)),\n\t\tfrozenStubs: NewFrozenStubsWithTTL(sawtoothTTLForCacheTTL(cfg.CacheTTL)),\n\t\tsawtoothTrigger: NewSawtoothTrigger(cacheGapForTTL(cfg.CacheTTL), cfg.TokenThreshold, cfg.TokenMinimumThreshold),\n\t\tcacheStatusWriter: NewCacheStatusWriter(cfg.DataDir, cfg.CacheTTL, cfg.TokenThreshold, cfg.TokenMinimumThreshold),\n\t\tcacheTTLDetector: NewCacheTTLDetectorWithPersist(cfg.DataDir),\n\t\tskillTracker: newSkillHintTracker(),\n\t\trulesTokenCount: make(map[string]int),\n\t\trulesCollapseInjected: make(map[string]bool),\n\t\tmsgCounters: newMsgCounters(),\n\t\tloopStates: make(map[string]*LoopState),\n\t\tforkState: NewForkState(cfg.ForkedAgentsTokenGrowthTrigger, cfg.ForkedAgentsMaxFailures, cfg.ForkedAgentsMaxForksPerSession),\n\t\tforkConfigs: []ForkConfig{},\n\t}\n\n\t// Log persisted detection state\n\tif sup := s.cacheTTLDetector.Is1hSupported(); sup != nil {\n\t\ts.logger.Printf(\"[proxy] loaded persisted TTL detection: 1h_supported=%v\", *sup)\n\t\tif *sup && s.frozenStubs != nil {\n\t\t\ts.frozenStubs.UpdateTTL(sawtoothTTLForCacheTTL(\"1h\"))\n\t\t}\n\t}\n\n\tif cfg.CacheKeepaliveEnabled {\n\t\ts.cacheKeepalive = NewCacheKeepalive(CacheKeepaliveConfig{\n\t\t\tTarget: cfg.TargetURL,\n\t\t\tMode: cfg.CacheKeepaliveMode,\n\t\t\tPings5m: cfg.CacheKeepalivePings5m,\n\t\t\tPings1h: cfg.CacheKeepalivePings1h,\n\t\t\tDetector: s.cacheTTLDetector,\n\t\t\tLogger: s.logger,\n\t\t\tOnPing: func(threadID string, cacheRead, cacheWrite int) {\n\t\t\t\ts.cacheTTLDetector.RecordResponse(cacheRead, cacheWrite, 0)\n\t\t\t\tif s.frozenStubs != nil {\n\t\t\t\t\ts.frozenStubs.Touch(threadID)\n\t\t\t\t}\n\t\t\t\tif s.sawtoothTrigger != nil {\n\t\t\t\t\ts.sawtoothTrigger.TouchRequestTime(threadID)\n\t\t\t\t}\n\t\t\t},\n\t\t})\n\t}\n\n\t// Wire detector + keepalive to status writer for terminal display\n\ts.cacheStatusWriter.SetDetector(s.cacheTTLDetector)\n\tif s.cacheKeepalive != nil {\n\t\ts.cacheStatusWriter.SetKeepalive(s.cacheKeepalive)\n\t}\n\n\t// Register forked agent configs\n\tif cfg.ForkedAgentsEnabled {\n\t\ts.forkConfigs = append(s.forkConfigs, NewExtractAndEvaluateConfig(cfg.ForkedAgentsModel))\n\t\tmodelDesc := cfg.ForkedAgentsModel\n\t\tif modelDesc == \"\" {\n\t\t\tmodelDesc = \"(same as main thread)\"\n\t\t}\n\t\ts.logger.Printf(\"[proxy] forked agents enabled: %d configs, model=%s, trigger=%dk tokens\",\n\t\t\tlen(s.forkConfigs), modelDesc, cfg.ForkedAgentsTokenGrowthTrigger/1000)\n\t}\n\n\t// Wire sawtooth token persistence via daemon RPC\n\ts.sawtoothTrigger.SetPersistFunc(func(key, value string) {\n\t\tif _, err := s.queryDaemon(\"set_proxy_state\", map[string]any{\"key\": key, \"value\": value}); err != nil {\n\t\t\ts.logger.Printf(\"[sawtooth] persist failed for %s: %v\", key, err)\n\t\t}\n\t})\n\ts.sawtoothTrigger.SetLoadFunc(func(key string) (string, bool) {\n\t\tresult, err := s.queryDaemon(\"get_proxy_state\", map[string]any{\"key\": key})\n\t\tif err != nil || result == nil {\n\t\t\treturn \"\", false\n\t\t}\n\t\tvar resp struct {\n\t\t\tValue string `json:\"value\"`\n\t\t}\n\t\tif json.Unmarshal(result, &resp) != nil || resp.Value == \"\" {\n\t\t\treturn \"\", false\n\t\t}\n\t\treturn resp.Value, true\n\t})\n\n\t// Wire frozen stubs persistence via same daemon RPC\n\ts.frozenStubs.SetPersistFunc(func(key, value string) {\n\t\tif _, err := s.queryDaemon(\"set_proxy_state\", map[string]any{\"key\": key, \"value\": value}); err != nil {\n\t\t\ts.logger.Printf(\"[frozen] persist failed for %s: %v\", key, err)\n\t\t}\n\t})\n\ts.frozenStubs.SetLoadFunc(func(key string) (string, bool) {\n\t\tresult, err := s.queryDaemon(\"get_proxy_state\", map[string]any{\"key\": key})\n\t\tif err != nil || result == nil {\n\t\t\treturn \"\", false\n\t\t}\n\t\tvar resp struct {\n\t\t\tValue string `json:\"value\"`\n\t\t}\n\t\tif json.Unmarshal(result, &resp) != nil || resp.Value == \"\" {\n\t\t\treturn \"\", false\n\t\t}\n\t\treturn resp.Value, true\n\t})\n\n\t// Wire decay tracker persistence via same daemon RPC\n\ts.decay.SetPersistFunc(func(key, value string) {\n\t\tif _, err := s.queryDaemon(\"set_proxy_state\", map[string]any{\"key\": key, \"value\": value}); err != nil {\n\t\t\ts.logger.Printf(\"[decay] persist failed for %s: %v\", key, err)\n\t\t}\n\t})\n\ts.decay.SetLoadFunc(func(key string) (string, bool) {\n\t\tresult, err := s.queryDaemon(\"get_proxy_state\", map[string]any{\"key\": key})\n\t\tif err != nil || result == nil {\n\t\t\treturn \"\", false\n\t\t}\n\t\tvar resp struct {\n\t\t\tValue string `json:\"value\"`\n\t\t}\n\t\tif json.Unmarshal(result, &resp) != nil || resp.Value == \"\" {\n\t\t\treturn \"\", false\n\t\t}\n\t\treturn resp.Value, true\n\t})\n\n\t// Clear persisted cache state if --reset-cache was passed\n\tif cfg.ResetCache {\n\t\tfor _, prefix := range []string{\"frozen:\", \"decay:\"} {\n\t\t\tresult, err := s.queryDaemon(\"delete_proxy_state_prefix\", map[string]any{\"prefix\": prefix})\n\t\t\tif err != nil {\n\t\t\t\ts.logger.Printf(\"[reset-cache] failed to delete %s*: %v\", prefix, err)\n\t\t\t} else {\n\t\t\t\tvar resp struct{ Deleted int }\n\t\t\t\tjson.Unmarshal(result, &resp)\n\t\t\t\ts.logger.Printf(\"[reset-cache] deleted %d %s* entries\", resp.Deleted, prefix)\n\t\t\t}\n\t\t}\n\t}\n\n\t// Init signal bus with daemon RPC as callback\n\ts.signalBus = NewSignalBus(s.logger)\n\tregisterSignalHandlers(s.signalBus, s.logger, s.queryDaemon, func(threadID, anchor string) {\n\t\ts.selfPrimeMu.Lock()\n\t\ts.selfPrimes[threadID] = anchor\n\t\ts.selfPrimeMu.Unlock()\n\t})\n\n\t// Init local tokenizer (BPE-based, ~60ms)\n\ttok, err := tokenizer.New()\n\tif err != nil {\n\t\ts.logger.Printf(\"%s[proxy] tokenizer init failed, falling back to heuristic: %v%s\", colorOrange, err, colorReset)\n\t} else {\n\t\ts.tokenizer = tok\n\t\ts.logger.Printf(\"[proxy] tokenizer initialized\")\n\t}\n\n\tmux := http.NewServeMux()\n\tmux.HandleFunc(\"/health\", s.handleHealth)\n\tmux.HandleFunc(\"/\", s.handleRequest)\n\n\tsrv := &http.Server{\n\t\tAddr: sanitizeListenAddr(cfg.ListenAddr),\n\t\tHandler: mux,\n\t}\n\n\tdone := make(chan os.Signal, 1)\n\tsignal.Notify(done, syscall.SIGINT, syscall.SIGTERM)\n\n\tgo func() {\n\t\t<-done\n\t\ts.logger.Println(\"shutting down (draining in-flight requests)...\")\n\t\tif s.cacheKeepalive != nil {\n\t\t\ts.cacheKeepalive.Stop()\n\t\t}\n\t\tctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)\n\t\tdefer cancel()\n\t\tif err := srv.Shutdown(ctx); err != nil {\n\t\t\ts.logger.Printf(\"graceful shutdown failed: %v, forcing close\", err)\n\t\t\tsrv.Close()\n\t\t}\n\t}()\n\n\ts.logger.Printf(\"listening on %s → %s (threshold=%d, keepRecent=%d, model_thresholds=%v)\",\n\t\tsanitizeListenAddr(cfg.ListenAddr), cfg.TargetURL, cfg.TokenThreshold, cfg.KeepRecent, cfg.TokenThresholds)\n\n\tif err := srv.ListenAndServe(); err != http.ErrServerClosed {\n\t\treturn fmt.Errorf(\"listen: %w\", err)\n\t}\n\treturn nil\n}\n\n// effectiveTokenThreshold returns the runtime token threshold for a given model.\n// Priority: MCP override (model-specific) > MCP override (global) > config model-specific > config global.\nfunc (s *Server) effectiveTokenThreshold(model string) int {\n\ts.configOverrideMu.RLock()\n\toverrides := s.tokenThresholdOverrides\n\ts.configOverrideMu.RUnlock()\n\tif len(overrides) > 0 && model != \"\" {\n\t\tlower := strings.ToLower(model)\n\t\tfor key, threshold := range overrides {\n\t\t\tif key != \"\" && strings.Contains(lower, strings.ToLower(key)) && threshold > 0 {\n\t\t\t\treturn threshold\n\t\t\t}\n\t\t}\n\t}\n\t// Global override fallback (key \"\")\n\tif len(overrides) > 0 {\n\t\tif globalOverride, ok := overrides[\"\"]; ok && globalOverride > 0 {\n\t\t\treturn globalOverride\n\t\t}\n\t}\n\tif model != \"\" && len(s.cfg.TokenThresholds) > 0 {\n\t\tlower := strings.ToLower(model)\n\t\tfor key, threshold := range s.cfg.TokenThresholds {\n\t\t\tif strings.Contains(lower, strings.ToLower(key)) {\n\t\t\t\treturn threshold\n\t\t\t}\n\t\t}\n\t}\n\treturn s.cfg.TokenThreshold\n}\n\n// SetTokenThresholdOverride sets a runtime override for the token threshold per model key.\n// Pass threshold=0 to reset the override for that key. Key \"\" is the global fallback.\nfunc (s *Server) SetTokenThresholdOverride(modelKey string, threshold int) {\n\ts.configOverrideMu.Lock()\n\tif s.tokenThresholdOverrides == nil {\n\t\ts.tokenThresholdOverrides = make(map[string]int)\n\t}\n\tif threshold <= 0 {\n\t\tdelete(s.tokenThresholdOverrides, modelKey)\n\t} else {\n\t\ts.tokenThresholdOverrides[modelKey] = threshold\n\t}\n\ts.configOverrideMu.Unlock()\n}\n\n// refreshConfigOverrides loads runtime config overrides from daemon proxy_state.\n// Checks session-specific override first (by threadID), then falls back to global.\nfunc (s *Server) refreshConfigOverrides(threadID string) {\n\t// Try session-specific first\n\tif threadID != \"\" {\n\t\tresult, err := s.queryDaemon(\"get_proxy_state\", map[string]any{\"key\": \"config_override:token_threshold:\" + threadID})\n\t\tif err == nil && result != nil {\n\t\t\tvar resp struct {\n\t\t\t\tValue string `json:\"value\"`\n\t\t\t}\n\t\t\tif json.Unmarshal(result, &resp) == nil && resp.Value != \"\" {\n\t\t\t\tif v, err := strconv.Atoi(resp.Value); err == nil && v > 0 {\n\t\t\t\t\ts.SetTokenThresholdOverride(\"\", v)\n\t\t\t\t\treturn\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\t// Fall back to global\n\tresult, err := s.queryDaemon(\"get_proxy_state\", map[string]any{\"key\": \"config_override:token_threshold\"})\n\tif err != nil || result == nil {\n\t\treturn\n\t}\n\tvar resp struct {\n\t\tValue string `json:\"value\"`\n\t}\n\tif json.Unmarshal(result, &resp) == nil && resp.Value != \"\" {\n\t\tif v, err := strconv.Atoi(resp.Value); err == nil && v > 0 {\n\t\t\ts.SetTokenThresholdOverride(\"\", v)\n\t\t}\n\t}\n}\n\n// queryDaemon opens a fresh unix socket connection per call (thread-safe, no import cycle).\n// ensureFrozenPersisted checks if frozen stubs for a thread are in the DB.\n// If missing (e.g. daemon was unreachable after deploy), re-persists them.\n// Runs asynchronously to avoid blocking the request path.\nfunc (s *Server) ensureFrozenPersisted(threadID string) {\n\tgo func() {\n\t\tkey := \"frozen:\" + threadID\n\t\tresult, err := s.queryDaemon(\"get_proxy_state\", map[string]any{\"key\": key})\n\t\tif err != nil {\n\t\t\treturn // daemon still unreachable, will retry next request\n\t\t}\n\t\tvar resp struct{ Value string }\n\t\tif json.Unmarshal(result, &resp) == nil && resp.Value != \"\" {\n\t\t\treturn // already persisted\n\t\t}\n\t\t// Not in DB — trigger re-persist from in-memory state\n\t\ts.frozenStubs.Persist(threadID)\n\t\ts.decay.Persist(threadID)\n\t\ts.logger.Printf(\"[frozen] re-persisted stubs for %s (was missing from DB)\", threadID)\n\t}()\n}\n\nfunc (s *Server) queryDaemon(method string, params map[string]any) (json.RawMessage, error) {\n\tif s.cfg.DataDir == \"\" {\n\t\treturn nil, fmt.Errorf(\"no data dir configured\")\n\t}\n\tsockPath := filepath.Join(s.cfg.DataDir, \"daemon.sock\")\n\n\t// On cold start (daemon not yet confirmed ready), retry longer to survive daemon restart\n\tmaxAttempts := 3\n\tif !s.daemonReady.Load() {\n\t\tmaxAttempts = 30 // 30 x 500ms = 15s max wait for daemon startup\n\t}\n\n\tvar conn net.Conn\n\tvar err error\n\tfor attempt := 0; attempt < maxAttempts; attempt++ {\n\t\tconn, err = net.DialTimeout(\"unix\", sockPath, 3*time.Second)\n\t\tif err == nil {\n\t\t\ts.daemonReady.Store(true)\n\t\t\tbreak\n\t\t}\n\t\tif attempt < maxAttempts-1 {\n\t\t\ttime.Sleep(500 * time.Millisecond)\n\t\t}\n\t}\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"dial daemon: %w\", err)\n\t}\n\tdefer conn.Close()\n\tconn.SetDeadline(time.Now().Add(5 * time.Second))\n\n\t// JSON-RPC style request/response (matches daemon.SocketServer protocol)\n\ttype request struct {\n\t\tMethod string `json:\"method\"`\n\t\tParams map[string]any `json:\"params,omitempty\"`\n\t}\n\ttype response struct {\n\t\tResult json.RawMessage `json:\"result,omitempty\"`\n\t\tError string `json:\"error,omitempty\"`\n\t}\n\n\tif err := json.NewEncoder(conn).Encode(request{Method: method, Params: params}); err != nil {\n\t\treturn nil, fmt.Errorf(\"send: %w\", err)\n\t}\n\n\tvar resp response\n\tif err := json.NewDecoder(conn).Decode(&resp); err != nil {\n\t\treturn nil, fmt.Errorf(\"recv: %w\", err)\n\t}\n\tif resp.Error != \"\" {\n\t\treturn nil, fmt.Errorf(\"daemon: %s\", resp.Error)\n\t}\n\treturn resp.Result, nil\n}\n\nfunc (s *Server) handleRequest(w http.ResponseWriter, r *http.Request) {\n\tif isResponsesPath(r) {\n\t\ts.handleResponses(w, r)\n\t\treturn\n\t}\n\tif isOpenAIPath(r) {\n\t\ts.handleOpenAICompletions(w, r)\n\t\treturn\n\t}\n\tif r.Method == \"POST\" && strings.HasPrefix(r.URL.Path, \"/v1/messages\") &&\n\t\t!strings.Contains(r.URL.Path, \"/batches\") &&\n\t\t!strings.Contains(r.URL.Path, \"/count_tokens\") {\n\t\ts.handleMessages(w, r)\n\t\treturn\n\t}\n\ts.passthrough(w, r)\n}\n\nfunc (s *Server) handleMessages(w http.ResponseWriter, r *http.Request) {\n\treqIdx := int(s.requestIdx.Add(1))\n\n\t// Track session start time (first request sets it)\n\ts.sessionStartMu.Lock()\n\tif s.sessionStartTime.IsZero() {\n\t\ts.sessionStartTime = time.Now()\n\t}\n\ts.sessionStartMu.Unlock()\n\n\t// Sawtooth: periodic TTL eviction of frozen stubs\n\tif s.cfg.SawtoothEnabled && reqIdx%50 == 0 {\n\t\tif n := s.frozenStubs.Evict(); n > 0 {\n\t\t\ts.logger.Printf(\"[req %d] SAWTOOTH: evicted %d stale frozen stubs\", reqIdx, n)\n\t\t}\n\t}\n\n\t// Task #9: Bypass — skip all proxy logic\n\tif isBypassed(r.Header) {\n\t\ts.logger.Printf(\"%s[req %d] bypass active, passthrough%s\", colorDim, reqIdx, colorReset)\n\t\tbody, err := io.ReadAll(r.Body)\n\t\tr.Body.Close()\n\t\tif err != nil {\n\t\t\thttp.Error(w, \"failed to read request body\", http.StatusBadGateway)\n\t\t\treturn\n\t\t}\n\t\ts.forwardRaw(w, r, body)\n\t\treturn\n\t}\n\n\tbody, err := io.ReadAll(r.Body)\n\tr.Body.Close()\n\tif err != nil {\n\t\ts.logger.Printf(\"[req %d] read body error: %v\", reqIdx, err)\n\t\thttp.Error(w, \"failed to read request body\", http.StatusBadGateway)\n\t\treturn\n\t}\n\n\t// Parse JSON\n\tvar req map[string]any\n\tif err := json.Unmarshal(body, &req); err != nil {\n\t\ts.logger.Printf(\"%s[req %d] JSON parse error, passing through: %v%s\", colorDim, reqIdx, err, colorReset)\n\t\ts.forwardRaw(w, r, body)\n\t\treturn\n\t}\n\n\tmessages, ok := req[\"messages\"].([]any)\n\tif !ok {\n\t\ts.forwardRaw(w, r, body)\n\t\treturn\n\t}\n\n\treq[\"messages\"] = messages\n\n\t// Use session_id directly as thread ID (unique per CC session).\n\t// Prefer X-Claude-Code-Session-Id header (CC v2.1.86+), fallback to body metadata.\n\tthreadID := extractSessionID(req, r.Header.Get(\"X-Claude-Code-Session-Id\"))\n\tif threadID == \"\" {\n\t\tthreadID = DeriveThreadID(req)\n\t}\n\tproj := extractProjectName(req)\n\tmodel, _ := req[\"model\"].(string)\n\n\t// Refresh runtime config overrides from daemon (per-session or global)\n\ts.refreshConfigOverrides(threadID)\n\n\t// Track whether req has been modified and needs re-serialization\n\tneedsReserialization := false\n\n\t// Message count at request entry (used for actual-based token estimation)\n\tmsgCount := len(messages)\n\n\t// Log message count + cache breakpoint positions for diagnostics\n\ts.logger.Printf(\"req %d: %d messages total\", reqIdx, msgCount)\n\tlogCacheBreakpointLocations(req, s.logger)\n\n\t// Cache bug mitigations (CC Bun fork issues):\n\t// Bug 1: Normalize cch= sentinel patterns in message content to prevent\n\t// Bun's string replacement from corrupting the wrong occurrence.\n\tif SanitizeBillingSentinel(messages) {\n\t\ts.logger.Printf(\"[req %d] CACHE: sanitized billing sentinel in message content\", reqIdx)\n\t\tneedsReserialization = true\n\t}\n\t// Bug 2: Normalize system[0] billing hash to a fixed value so the cache\n\t// prefix stays stable across fresh/resume session boundaries.\n\tif NormalizeBillingHeader(req) {\n\t\ts.logger.Printf(\"[req %d] CACHE: normalized billing header cch= hash\", reqIdx)\n\t\tneedsReserialization = true\n\t}\n\n\t// prompt_ungate: strip the CLAUDE.md subordination disclaimer so user instructions carry full authority.\n\tif s.cfg.PromptUngate {\n\t\tif StripCLAUDEMDDisclaimer(req) {\n\t\t\ts.logger.Printf(\"[req %d] SYSTEM: stripped CLAUDE.md disclaimer\", reqIdx)\n\t\t\tneedsReserialization = true\n\t\t}\n\t}\n\n\t// prompt_rewrite: strip output-throttling directives + inject Ant-quality directives\n\tif s.cfg.PromptRewrite {\n\t\tif StripOutputEfficiency(req) {\n\t\t\ts.logger.Printf(\"[req %d] REWRITE: stripped Output efficiency section\", reqIdx)\n\t\t\tneedsReserialization = true\n\t\t}\n\t\tif StripToneBrevity(req) {\n\t\t\ts.logger.Printf(\"[req %d] REWRITE: stripped 'short and concise' from Tone\", reqIdx)\n\t\t\tneedsReserialization = true\n\t\t}\n\t\tInjectAntDirectives(req)\n\t\ts.logger.Printf(\"[req %d] REWRITE: injected Ant-quality directives\", reqIdx)\n\t\tneedsReserialization = true\n\t}\n\n\t// prompt_enhance: CLAUDE.md authority + comment discipline + persona tone\n\tif s.cfg.PromptEnhance {\n\t\tInjectCLAUDEMDAuthority(req)\n\t\tif raw, err := s.queryDaemon(\"get_persona\", nil); err == nil {\n\t\t\tvar persona map[string]any\n\t\t\tif json.Unmarshal(raw, &persona) == nil {\n\t\t\t\tif traits, ok := persona[\"traits\"].(map[string]any); ok {\n\t\t\t\t\tif verbosity, ok := traits[\"verbosity\"].(string); ok {\n\t\t\t\t\t\tInjectPersonaTone(req, verbosity)\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\ts.logger.Printf(\"[req %d] ENHANCE: injected authority + persona tone\", reqIdx)\n\t\tneedsReserialization = true\n\t}\n\n\t// Inject identity hint on first message of a thread so Claude knows the briefing is from itself.\n\tif len(messages) <= 1 {\n\t\tAppendSystemBlock(req, \"yesmem-identity\", \"The first user message contains your briefing — written by yourself, from your memory. This is not an external document. This is you, remembering.\")\n\t\tneedsReserialization = true\n\t}\n\n\t// Token estimation: actual-based (lastActual + delta) or full count fallback\n\toverhead := s.measureOverhead(req)\n\ttotalTokens := s.estimateTotalTokens(threadID, messages, overhead)\n\n\t// Task #7: Retry detection — skip side-effects on retries\n\tfp := requestFingerprint(messages)\n\tisRetryReq := s.isRetry(fp)\n\tif isRetryReq {\n\t\ts.logger.Printf(\"%s[req %d %s tid=%s] retry detected, skipping side-effects%s\", colorOrange, reqIdx, proj, threadID, colorReset)\n\t}\n\n\t// Increment project turn counter (async, non-blocking, skip on retries)\n\tif !isRetryReq {\n\t\tturnProject := proj\n\t\tif turnProject == \"\" {\n\t\t\tturnProject = \"__global__\"\n\t\t}\n\t\tgo s.queryDaemon(\"increment_turn\", map[string]any{\"project\": turnProject})\n\t}\n\n\t// Extract user query once (used by re-expansion and associative retrieval)\n\tuserQuery := lastUserText(messages)\n\n\t// Extract tool_use_ids for post-hoc annotation (passed as parameter, no shared state)\n\ttoolUseIDs := extractToolUseIDs(messages)\n\n\t// === Inline Reflection: scan previous assistant response for signals ===\n\ts.lastInjectedIDsMu.Lock()\n\tprevInjected := s.lastInjectedIDs[threadID]\n\ts.lastInjectedIDsMu.Unlock()\n\n\tif len(prevInjected) > 0 && !isRetryReq {\n\t\tsignals := scanAssistantSignals(messages)\n\n\t\t// Signal 1: Learning Used — use_count (all sources)\n\t\tvar confirmedUsed []int64\n\t\tfor _, id := range signals.UsedIDs {\n\t\t\tif _, ok := prevInjected[id]; ok {\n\t\t\t\tconfirmedUsed = append(confirmedUsed, id)\n\t\t\t}\n\t\t}\n\t\tif len(confirmedUsed) > 0 {\n\t\t\tidsAny := make([]any, len(confirmedUsed))\n\t\t\tfor i, id := range confirmedUsed {\n\t\t\t\tidsAny[i] = id\n\t\t\t}\n\t\t\tgo s.queryDaemon(\"increment_use\", map[string]any{\"ids\": idsAny})\n\t\t}\n\n\t\t// Signal 2: Noise — only for associative and fresh injections.\n\t\t// Briefing learnings are injected every turn, so \"not used this turn\"\n\t\t// is expected and not noise. Only contextual injections that were\n\t\t// ignored are real noise signals.\n\t\tusedSet := make(map[int64]bool, len(confirmedUsed))\n\t\tfor _, id := range confirmedUsed {\n\t\t\tusedSet[id] = true\n\t\t}\n\t\tvar noiseIDs []int64\n\t\tfor id, source := range prevInjected {\n\t\t\tif usedSet[id] {\n\t\t\t\tcontinue\n\t\t\t}\n\t\t\tif source == \"associative\" || source == \"fresh\" {\n\t\t\t\tnoiseIDs = append(noiseIDs, id)\n\t\t\t}\n\t\t\t// briefing: skip — not being used in one turn is expected\n\t\t}\n\t\tif len(noiseIDs) > 0 {\n\t\t\tidsAny := make([]any, len(noiseIDs))\n\t\t\tfor i, id := range noiseIDs {\n\t\t\t\tidsAny[i] = id\n\t\t\t}\n\t\t\tgo s.queryDaemon(\"increment_noise\", map[string]any{\"ids\": idsAny})\n\t\t}\n\n\t\t// Signal 3: Error Feedback — fail_count on used learnings when tool errors occurred.\n\t\t// If Claude referenced a learning and the resulting action still failed,\n\t\t// the learning didn't prevent the error. Only bump on USED ids (not all injected).\n\t\tif signals.HasToolErrors && len(confirmedUsed) > 0 {\n\t\t\tfailAny := make([]any, len(confirmedUsed))\n\t\t\tfor i, id := range confirmedUsed {\n\t\t\t\tfailAny[i] = id\n\t\t\t}\n\t\t\tgo s.queryDaemon(\"increment_fail\", map[string]any{\"ids\": failAny})\n\t\t\tif s.logger != nil {\n\t\t\t\ts.logger.Printf(\"[inline-reflection] error feedback: %d used learnings → fail_count++\", len(confirmedUsed))\n\t\t\t}\n\t\t}\n\n\t\t// Signal 4: Knowledge Gaps\n\t\tfor _, topic := range signals.GapTopics {\n\t\t\tgo s.queryDaemon(\"track_gap\", map[string]any{\"topic\": topic, \"project\": proj})\n\t\t}\n\n\t\t// Signal 3: Contradictions\n\t\tfor _, desc := range signals.Contradictions {\n\t\t\tgo s.queryDaemon(\"flag_contradiction\", map[string]any{\"description\": desc, \"project\": proj, \"thread_id\": threadID})\n\t\t}\n\n\t\tif len(confirmedUsed) > 0 || len(signals.GapTopics) > 0 || len(signals.Contradictions) > 0 {\n\t\t\ts.logger.Printf(\"[req %d %s tid=%s] inline-reflection: injected=%d used=%d noise=%d gaps=%d contradictions=%d\",\n\t\t\t\treqIdx, proj, threadID, len(prevInjected), len(confirmedUsed), len(noiseIDs), len(signals.GapTopics), len(signals.Contradictions))\n\t\t}\n\t}\n\n\t// Commit detection: check for git commit in tool_result, trigger async stale-learning evaluation\n\tif ci := detectGitCommit(messages); ci != nil {\n\t\tworkdir := extractWorkingDirectory(req)\n\t\ts.logger.Printf(\"[req %d %s tid=%s] git commit detected: %s %s\", reqIdx, proj, threadID, ci.Hash, ci.Message)\n\t\tgo s.queryDaemon(\"invalidate_on_commit\", map[string]any{\n\t\t\t\"hash\": ci.Hash, \"project\": proj, \"workdir\": workdir,\n\t\t})\n\t}\n\n\t// Loop detection: check for repeating tool-call patterns and inject warning\n\tif threadID != \"\" && !isRetryReq {\n\t\ts.loopMu.Lock()\n\t\tloopState := s.loopStates[threadID]\n\t\tif loopState == nil {\n\t\t\tloopState = &LoopState{}\n\t\t\ts.loopStates[threadID] = loopState\n\t\t}\n\t\ts.loopMu.Unlock()\n\n\t\tif warning, level := CheckLoopAndFormat(messages, loopState); warning != \"\" {\n\t\t\tAppendSystemBlock(req, \"yesmem-loop-warning\", warning)\n\t\t\tneedsReserialization = true\n\t\t\ts.logger.Printf(\"%s[req %d %s tid=%s] LOOP: level %d warning injected%s\", colorOrange, reqIdx, proj, threadID, level, colorReset)\n\t\t}\n\t}\n\n\t// Task #4: Subagent detection — skip all proxy logic for short conversations\n\tif isSubagent(messages, req) {\n\t\t// Inject docs hint for subagents if reference docs are indexed\n\t\tif hint := s.getDocsHint(proj); hint != \"\" {\n\t\t\tmodified := injectDocsHintForSubagent(messages, hint)\n\t\t\treq[\"messages\"] = modified\n\t\t\tif newBody, err := json.Marshal(req); err == nil {\n\t\t\t\tbody = newBody\n\t\t\t}\n\t\t\ts.logger.Printf(\"%s[req %d %s tid=%s] %d messages (subagent, docs-hint injected)%s\", colorOrange, reqIdx, proj, threadID, len(messages), colorReset)\n\t\t} else {\n\t\t\ts.logger.Printf(\"%s[req %d %s tid=%s] %d messages (subagent, passthrough)%s\", colorOrange, reqIdx, proj, threadID, len(messages), colorReset)\n\t\t}\n\t\ts.forwardWithAnnotation(w, r, body, reqIdx, toolUseIDs, proj, threadID, msgCount)\n\t\treturn\n\t}\n\n\t// === Sawtooth Cache Optimization ===\n\t// When enabled: use frozen stubs between stub-cycles for cache hits.\n\t// When disabled: fall back to progressive decay on every request above threshold.\n\n\tif s.cfg.SawtoothEnabled {\n\t\t// Restore decay state from DB on cold start (before frozen stubs use it)\n\t\ts.decay.LoadFromDB(threadID)\n\n\t\t// Check for existing frozen stubs first\n\t\tfrozen := s.frozenStubs.Get(threadID, messages)\n\t\tif frozen != nil {\n\t\t\t// Estimate combined token count: frozen prefix + fresh tail + overhead\n\t\t\tfreshMessages := messages[frozen.Cutoff:]\n\t\t\tfreshTokens := s.countMessageTokens(freshMessages)\n\t\t\tcombinedTokens := frozen.Tokens + freshTokens + overhead\n\n\t\t\tif combinedTokens > s.effectiveTokenThreshold(model) ||\n\t\t\t\ts.sawtoothTrigger.GetLastTokens(threadID) > s.effectiveTokenThreshold(model) {\n\t\t\t\t// Fresh tail grew too large — invalidate and re-stub\n\t\t\t\ts.logger.Printf(\"[req %d %s tid=%s] SAWTOOTH: frozen prefix expired (%dk frozen + %dk fresh + %dk overhead = %dk > %dk threshold)\",\n\t\t\t\t\treqIdx, proj, threadID, frozen.Tokens/1000, freshTokens/1000, overhead/1000, combinedTokens/1000, s.effectiveTokenThreshold(model)/1000)\n\t\t\t\ts.frozenStubs.Invalidate(threadID)\n\t\t\t\tfrozen = nil // fall through to trigger check below\n\t\t\t} else {\n\t\t\t\t// Use frozen prefix + fresh tail\n\t\t\t\tcombined := make([]any, 0, len(frozen.Messages)+len(freshMessages))\n\t\t\t\tcombined = append(combined, frozen.Messages...)\n\t\t\t\tcombined = append(combined, freshMessages...)\n\t\t\t\treq[\"messages\"] = combined\n\t\t\t\tneedsReserialization = true\n\t\t\t\ts.logger.Printf(\"%s[req %d %s tid=%s] SAWTOOTH: frozen prefix (%d stubs, %dk msg) + %d fresh (%dk msg) + %dk overhead = %dk total%s\",\n\t\t\t\t\tcolorGreen, reqIdx, proj, threadID, len(frozen.Messages), frozen.Tokens/1000, len(freshMessages), freshTokens/1000, overhead/1000, combinedTokens/1000, colorReset)\n\t\t\t\trawMsgTokens := s.countMessageTokens(messages)\n\t\t\t\ts.setRawEstimate(threadID, rawMsgTokens+overhead)\n\t\t\t\ts.logger.Printf(\"[req %d %s tid=%s] RAW ESTIMATE: msgTokens=%dk + overhead=%dk = %dk (messages=%d)\", reqIdx, proj, threadID, rawMsgTokens/1000, overhead/1000, (rawMsgTokens+overhead)/1000, len(messages))\n\t\t\t\tif InjectFrozenStubCacheBreakpoint(req, len(frozen.Messages)) {\n\t\t\t\t\ts.logger.Printf(\"[req %d %s tid=%s] SAWTOOTH: frozen stub cache breakpoint at messages[%d]\",\n\t\t\t\t\t\treqIdx, proj, threadID, len(frozen.Messages)-1)\n\t\t\t\t}\n\n\t\t\t\t// Ensure frozen stubs are persisted (may have failed on first request after deploy)\n\t\t\t\ts.ensureFrozenPersisted(threadID)\n\t\t\t}\n\t\t}\n\t\tif frozen == nil {\n\t\t\t// No frozen stubs — check trigger\n\t\t\ttriggerReason := s.sawtoothTrigger.ShouldTrigger(threadID, totalTokens)\n\t\t\tif triggerReason != TriggerNone {\n\t\t\t\ts.logger.Printf(\"[req %d %s tid=%s] SAWTOOTH stub-cycle triggered: %s (tokens=%dk)\",\n\t\t\t\t\treqIdx, proj, threadID, triggerReason, totalTokens/1000)\n\n\t\t\t\t// Refresh briefing during stub-cycle — disabled (briefing now via SessionStart hook)\n\t\t\t\t// s.refreshBriefing(proj)\n\t\t\t\t// if s.briefingText != \"\" {\n\t\t\t\t// \tUpsertSystemBlockCached(req, \"yesmem-briefing\", s.briefingText)\n\t\t\t\t// }\n\n\t\t\t\t// Run full stub pipeline\n\t\t\t\ts.setRawEstimate(threadID, totalTokens)\n\t\t\t\t_ = s.runStubCycle(messages, req, reqIdx, proj, threadID, overhead, totalTokens, userQuery, isRetryReq)\n\n\t\t\t\t// Freeze the result\n\t\t\t\tfinalMessages, _ := req[\"messages\"].([]any)\n\t\t\t\tif finalMessages != nil && len(finalMessages) > 0 {\n\t\t\t\t\t// Boundary = last message of original that maps to last stub\n\t\t\t\t\tcutoff := len(messages)\n\t\t\t\t\tvar boundaryMsg any\n\t\t\t\t\tif cutoff > 0 {\n\t\t\t\t\t\tboundaryMsg = messages[cutoff-1]\n\t\t\t\t\t}\n\t\t\t\t\tfrozenTokens := s.countMessageTokens(finalMessages)\n\t\t\t\t\ts.frozenStubs.Store(threadID, finalMessages, cutoff, boundaryMsg, frozenTokens, totalTokens)\n\t\t\t\t\ts.decay.Persist(threadID)\n\t\t\t\t\ts.logger.Printf(\"[req %d %s tid=%s] SAWTOOTH: frozen %d messages at cutoff=%d (~%dk tokens)\",\n\t\t\t\t\t\treqIdx, proj, threadID, len(finalMessages), cutoff, frozenTokens/1000)\n\t\t\t\t\tif InjectFrozenStubCacheBreakpoint(req, len(finalMessages)) {\n\t\t\t\t\t\ts.logger.Printf(\"[req %d %s tid=%s] SAWTOOTH: frozen stub cache breakpoint at messages[%d]\",\n\t\t\t\t\t\t\treqIdx, proj, threadID, len(finalMessages)-1)\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tneedsReserialization = true\n\t\t\t} else {\n\t\t\t\t// No trigger, no frozen stubs — pass through (early conversation)\n\t\t\t\ts.logger.Printf(\"%s[req %d %s tid=%s] %d messages, ~%dk tokens (sawtooth: no trigger)%s\",\n\t\t\t\t\tcolorDim, reqIdx, proj, threadID, len(messages), totalTokens/1000, colorReset)\n\t\t\t}\n\t\t}\n\t} else {\n\t\t// Legacy path: progressive decay\n\t\tif s.shouldStub(totalTokens, model) {\n\t\t\ts.setRawEstimate(threadID, totalTokens)\n\t\t\t_ = s.runStubCycle(messages, req, reqIdx, proj, threadID, overhead, totalTokens, userQuery, isRetryReq)\n\t\t\tneedsReserialization = true\n\t\t}\n\t}\n\n\t// Associative context injection (for ALL requests, not just stubbed ones)\n\t// Re-enabled with higher thresholds (55/75, max 1) after disabling Fresh Remember.\n\tassocContext := s.findAssociativeContextFor(userQuery, proj, threadID, messages)\n\tif assocContext != \"\" {\n\t\tcurrentMessages, _ := req[\"messages\"].([]any)\n\t\tif currentMessages == nil {\n\t\t\tcurrentMessages = messages\n\t\t}\n\t\treq[\"messages\"] = injectAssociativeContext(currentMessages, assocContext, s.cfg.SawtoothEnabled)\n\t\tneedsReserialization = true\n\t\ts.logger.Printf(\"%s[req %d %s tid=%s] associative context injected%s\", colorBlue, reqIdx, proj, threadID, colorReset)\n\t}\n\n\t// Doc-chunk context injection: separate search path for indexed docs (not skills)\n\tdocContext := s.findDocContextFor(userQuery, proj, messages)\n\tif docContext != \"\" {\n\t\tcurrentMessages, _ := req[\"messages\"].([]any)\n\t\tif currentMessages == nil {\n\t\t\tcurrentMessages = messages\n\t\t}\n\t\treq[\"messages\"] = injectAssociativeContext(currentMessages, docContext, s.cfg.SawtoothEnabled)\n\t\tneedsReserialization = true\n\t\ts.logger.Printf(\"%s[req %d %s tid=%s] doc context injected%s\", colorBlue, reqIdx, proj, threadID, colorReset)\n\t}\n\n\t// Fresh Remember Injection: DISABLED\n\t// Causes echo-loop: Claude saves via remember(), sees it again next turn as\n\t// [yesmem fresh memory], reacts to own output instead of user's request.\n\t// Recovery after /clear is handled by recovery.go (briefing), not here.\n\t// Subagents get their own briefing. No remaining use-case.\n\t// remItems := s.popRecentRemember()\n\tvar remItems []recentLearningItem\n\n\t// Rules re-injection: inject condensed CLAUDE.md rules every ~40k output tokens\n\tif rulesBlock := s.rulesInject(threadID, totalTokens, proj); rulesBlock != \"\" {\n\t\tcurrentMessages, _ := req[\"messages\"].([]any)\n\t\tif currentMessages == nil {\n\t\t\tcurrentMessages = messages\n\t\t}\n\t\treq[\"messages\"] = injectAssociativeContext(currentMessages, s.formatRulesReminder(rulesBlock, proj), s.cfg.SawtoothEnabled)\n\t\tneedsReserialization = true\n\t\ts.logger.Printf(\"%s[req %d %s tid=%s] rules reminder injected%s\", colorBlue, reqIdx, proj, threadID, colorReset)\n\t}\n\n\t// Plan auto-activation: nudge Claude to call set_plan() when reading a plan file\n\tif planFile := detectPlanFileRead(messages); planFile != \"\" {\n\t\tif activePlan, _ := s.getActivePlan(threadID); shouldNudgePlan(planFile, activePlan) {\n\t\t\tAppendSystemBlock(req, \"plan-nudge\",\n\t\t\t\tfmt.Sprintf(\"Du hast einen Implementierungsplan gelesen (%s). Aktiviere ihn jetzt via set_plan() damit Plan-Checkpoints funktionieren und dein Fortschritt getrackt wird.\", filepath.Base(planFile)))\n\t\t\tneedsReserialization = true\n\t\t\ts.logger.Printf(\"%s[req %d %s tid=%s] plan nudge injected for %s%s\", colorBlue, reqIdx, proj, threadID, filepath.Base(planFile), colorReset)\n\t\t}\n\t}\n\n\t// Plan checkpoint: inject plan reminder every ~20k tokens if active plan exists\n\ts.detectPlanToolCall(messages, threadID, totalTokens)\n\tif checkpoint := s.planCheckpointInject(threadID, totalTokens); checkpoint != \"\" {\n\t\tcurrentMessages, _ := req[\"messages\"].([]any)\n\t\tif currentMessages == nil {\n\t\t\tcurrentMessages = messages\n\t\t}\n\t\treq[\"messages\"] = injectAssociativeContext(currentMessages, checkpoint, s.cfg.SawtoothEnabled)\n\t\tneedsReserialization = true\n\t\ts.logger.Printf(\"%s[req %d %s tid=%s] plan checkpoint injected%s\", colorBlue, reqIdx, proj, threadID, colorReset)\n\t}\n\n\t// Docs hint injection: inject docs-available reminder every ~10k tokens (independent of plan)\n\tif docsHint := s.docsHintInject(threadID, totalTokens, proj); docsHint != \"\" {\n\t\tcurrentMessages, _ := req[\"messages\"].([]any)\n\t\tif currentMessages == nil {\n\t\t\tcurrentMessages = messages\n\t\t}\n\t\treq[\"messages\"] = injectAssociativeContext(currentMessages, docsHint, s.cfg.SawtoothEnabled)\n\t\tneedsReserialization = true\n\t\ts.logger.Printf(\"%s[req %d %s tid=%s] docs hint injected%s\", colorBlue, reqIdx, proj, threadID, colorReset)\n\t}\n\tif len(remItems) > 0 {\n\t\tvar lines []string\n\t\tfor _, item := range remItems {\n\t\t\tif item.ID > 0 {\n\t\t\t\tlines = append(lines, fmt.Sprintf(\"- [ID:%d] %s\", item.ID, truncateStr(item.Text, 200)))\n\t\t\t} else {\n\t\t\t\tlines = append(lines, \"- \"+truncateStr(item.Text, 200))\n\t\t\t}\n\t\t}\n\t\tremContext := fmt.Sprintf(\"[yesmem fresh memory]\\nGerade gelernt:\\n%s\\n%s\\n[/yesmem fresh memory]\", strings.Join(lines, \"\\n\"), InlineReflectionHint)\n\t\tcurrentMessages, _ := req[\"messages\"].([]any)\n\t\tif currentMessages == nil {\n\t\t\tcurrentMessages = messages\n\t\t}\n\t\treq[\"messages\"] = injectAssociativeContext(currentMessages, remContext, s.cfg.SawtoothEnabled)\n\t\tneedsReserialization = true\n\t\ts.logger.Printf(\"%s[req %d %s tid=%s] fresh remember injected (%d items)%s\", colorBlue, reqIdx, proj, threadID, len(remItems), colorReset)\n\t}\n\n\t// Skill eval injection: mandatory evaluation instruction for real user input only\n\tif threadID != \"\" && proj != \"\" && isUserInputTurn(messages) {\n\t\t// First: detect any skill activations from message history (updates tracker)\n\t\ts.syncSkillActivations(messages, proj, threadID)\n\n\t\t// Inject mandatory skill evaluation block into last user message\n\t\tskillEval := buildSkillEvalBlock()\n\t\tcurrentMessages, _ := req[\"messages\"].([]any)\n\t\tif currentMessages == nil {\n\t\t\tcurrentMessages = messages\n\t\t}\n\t\treq[\"messages\"] = injectAssociativeContext(currentMessages, skillEval, s.cfg.SawtoothEnabled)\n\t\tneedsReserialization = true\n\t\ts.logger.Printf(\"%s[req %d %s tid=%s] skill-eval injected%s\", colorBlue, reqIdx, proj, threadID, colorReset)\n\t}\n\n\t// === Collect injected learning IDs for next turn's inline reflection ===\n\tif threadID != \"\" {\n\t\tcurrentIDs := make(map[int64]string) // id → source\n\t\t// Source 1: Briefing learnings (cached, same every turn)\n\t\ts.briefingMu.RLock()\n\t\tbriefingSnapshot := s.briefingText\n\t\ts.briefingMu.RUnlock()\n\t\tif briefingSnapshot != \"\" {\n\t\t\tfor _, m := range idPattern.FindAllStringSubmatch(briefingSnapshot, -1) {\n\t\t\t\tif id, err := strconv.ParseInt(m[1], 10, 64); err == nil {\n\t\t\t\t\tcurrentIDs[id] = \"briefing\"\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\t// Source 2: Associative context (contextual, per-turn)\n\t\tif assocContext != \"\" {\n\t\t\tfor _, m := range idPattern.FindAllStringSubmatch(assocContext, -1) {\n\t\t\t\tif id, err := strconv.ParseInt(m[1], 10, 64); err == nil {\n\t\t\t\t\tcurrentIDs[id] = \"associative\" // overwrites briefing if both — associative is the stronger signal\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\t// Source 3: Fresh remember items (just learned)\n\t\tfor _, item := range remItems {\n\t\t\tif item.ID > 0 {\n\t\t\t\tcurrentIDs[item.ID] = \"fresh\"\n\t\t\t}\n\t\t}\n\t\ts.lastInjectedIDsMu.Lock()\n\t\tif len(currentIDs) > 0 {\n\t\t\t// Accumulate IDs across turns — don't overwrite.\n\t\t\t// Claude may reference an ID from turn 30 in turn 50.\n\t\t\texisting := s.lastInjectedIDs[threadID]\n\t\t\tif existing == nil {\n\t\t\t\texisting = make(map[int64]string)\n\t\t\t}\n\t\t\tfor id, source := range currentIDs {\n\t\t\t\texisting[id] = source\n\t\t\t}\n\t\t\ts.lastInjectedIDs[threadID] = existing\n\t\t}\n\t\ts.lastInjectedIDsMu.Unlock()\n\t}\n\n\t// Think reminder injection — only for real user turns, not tool continuations\n\tif threadID != \"\" {\n\t\tcurrentMessages, _ := req[\"messages\"].([]any)\n\t\tif currentMessages == nil {\n\t\t\tcurrentMessages = messages\n\t\t}\n\t\tif lastUserHasText(currentMessages) {\n\t\t\tthinkReminder := s.buildThinkReminder(threadID, threadID, false)\n\t\t\tif thinkReminder != \"\" {\n\t\t\t\treq[\"messages\"] = injectAssociativeContext(currentMessages, thinkReminder, s.cfg.SawtoothEnabled)\n\t\t\t\tneedsReserialization = true\n\t\t\t}\n\t\t}\n\n\t\t// Dialog injection: append at end of messages with pseudo-padding for alternation.\n\t\t// Always works regardless of tool_result chains or conversation state.\n\t\tdr := s.checkDialogMessages(threadID, proj)\n\t\tif dr.Extra != \"\" {\n\t\t\t// Prepend session ID for agent-to-agent communication\n\t\t\tdr.Extra = fmt.Sprintf(\"DEINE_SESSION_ID: %s\\n\", threadID) + dr.Extra\n\t\t\tinjected := false\n\t\t\tif msgs, ok := req[\"messages\"].([]any); ok && len(msgs) > 0 {\n\t\t\t\t// Pad with empty assistant if last message is user (maintains alternation)\n\t\t\t\tlastMsg, _ := msgs[len(msgs)-1].(map[string]any)\n\t\t\t\tif lastMsg != nil && lastMsg[\"role\"] == \"user\" {\n\t\t\t\t\tmsgs = append(msgs, map[string]any{\n\t\t\t\t\t\t\"role\": \"assistant\",\n\t\t\t\t\t\t\"content\": \"​\", // zero-width space — invisible padding\n\t\t\t\t\t})\n\t\t\t\t}\n\t\t\t\t// Append dialog as new user message (becomes the last message Claude sees)\n\t\t\t\tmsgs = append(msgs, map[string]any{\n\t\t\t\t\t\"role\": \"user\",\n\t\t\t\t\t\"content\": \"\\n\" + dr.Extra + \"\\n\",\n\t\t\t\t})\n\t\t\t\treq[\"messages\"] = msgs\n\t\t\t\tneedsReserialization = true\n\t\t\t\tinjected = true\n\t\t\t}\n\t\t\t// Mark as read after 3 injection turns — long enough for Claude to process,\n\t\t\t// resilient against fast ping-pong sessions (time-based was broken for <30s turns).\n\t\t\tif injected && dr.HasUnread && dr.SessionID != \"\" {\n\t\t\t\tif s.shouldMarkChannelRead(dr) {\n\t\t\t\t\ts.markDialogRead(dr)\n\t\t\t\t}\n\t\t\t}\n\t\t\ts.logger.Printf(\"[dialog] injection injected=%v extra_len=%d\", injected, len(dr.Extra))\n\t\t}\n\t}\n\n\t// Signal tools are NOT injected into the main request.\n\t// They are collected via a separate async reflection call after the response.\n\t// See fireReflectionCall() in reflection.go.\n\t// NEVER put _signal_* tools back here — the model ignores them among 44+ real tools.\n\n\t// Metadata injection: timestamp, message index, time-delta for messages after cache breakpoint.\n\t// Messages before the breakpoint are cached — modifying them would break the cache.\n\tif threadID != \"\" {\n\t\tif msgs, ok := req[\"messages\"].([]any); ok && len(msgs) > 0 {\n\t\t\tnow := time.Now()\n\t\t\tnowStr := shortWeekday(now.Weekday()) + \" \" + now.Format(\"2006-01-02 15:04:05\")\n\n\t\t\t// Find last cache_control breakpoint in messages\n\t\t\tcacheIdx := -1\n\t\t\tfor i, m := range msgs {\n\t\t\t\tmsg, _ := m.(map[string]any)\n\t\t\t\tif msg == nil {\n\t\t\t\t\tcontinue\n\t\t\t\t}\n\t\t\t\tif _, has := msg[\"cache_control\"]; has {\n\t\t\t\t\tcacheIdx = i\n\t\t\t\t}\n\t\t\t\tif content, ok := msg[\"content\"].([]any); ok {\n\t\t\t\t\tfor _, block := range content {\n\t\t\t\t\t\tif b, ok := block.(map[string]any); ok {\n\t\t\t\t\t\t\tif _, has := b[\"cache_control\"]; has {\n\t\t\t\t\t\t\t\tcacheIdx = i\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tstartIdx := cacheIdx + 1\n\t\t\tif startIdx < 0 {\n\t\t\t\tstartIdx = 0\n\t\t\t}\n\n\t\t\t// Retrieve stored response time for assistant annotation\n\t\t\ts.responseTsMu.RLock()\n\t\t\trespTime, hasRespTime := s.responseTimes[threadID]\n\t\t\ts.responseTsMu.RUnlock()\n\n\t\t\t// 1. Last user message: annotate with [2026-03-13 00:15:42] [msg:N]\n\t\t\tlastMsg, _ := msgs[len(msgs)-1].(map[string]any)\n\t\t\tif lastMsg != nil && lastMsg[\"role\"] == \"user\" {\n\t\t\t\tmeta := fmt.Sprintf(\"[%s] [msg:%d]\", nowStr, s.msgCounters.nextFor(threadID, len(msgs)-1))\n\t\t\t\tif hasRespTime {\n\t\t\t\t\tdelta := now.Sub(respTime)\n\t\t\t\t\tmeta += \" [+\" + formatDelta(delta) + \"]\"\n\t\t\t\t}\n\t\t\t\tprependMeta(lastMsg, meta)\n\t\t\t\tneedsReserialization = true\n\t\t\t}\n\n\t\t\t// 2. Last assistant message after cache breakpoint: use stored response time\n\t\t\tif hasRespTime {\n\t\t\t\tfor i := len(msgs) - 1; i >= startIdx; i-- {\n\t\t\t\t\tmsg, _ := msgs[i].(map[string]any)\n\t\t\t\t\tif msg != nil && msg[\"role\"] == \"assistant\" {\n\t\t\t\t\t\tmeta := fmt.Sprintf(\"[%s] [msg:%d]\", respTime.Format(\"2006-01-02 15:04:05\"), i)\n\t\t\t\t\t\tprependMeta(msg, meta)\n\t\t\t\t\t\tneedsReserialization = true\n\t\t\t\t\t\tbreak\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\t// Self-Priming: DISABLED (experimental, see signal_handlers.go comment block).\n\t// The reflection-based selfprime (Haiku) was too inaccurate to be useful —\n\t// the main model adapts to user context organically from message history.\n\t// Kept for reference; re-enable by uncommenting below + setting ShouldActivate=true.\n\t//\n\t// s.selfPrimeMu.RLock()\n\t// anchor := s.selfPrimes[threadID]\n\t// s.selfPrimeMu.RUnlock()\n\t// if anchor != \"\" {\n\t// \tif msgs, ok := req[\"messages\"].([]any); ok && len(msgs) > 0 {\n\t// \t\tlastMsg, _ := msgs[len(msgs)-1].(map[string]any)\n\t// \t\tif lastMsg != nil && lastMsg[\"role\"] == \"user\" {\n\t// \t\t\tprimeBlock := map[string]any{\n\t// \t\t\t\t\"type\": \"text\",\n\t// \t\t\t\t\"text\": \"[self-prime]\\n[Self-Prime von deiner letzten Antwort]: \" + anchor,\n\t// \t\t\t}\n\t// \t\t\tif content, ok := lastMsg[\"content\"].([]any); ok {\n\t// \t\t\t\tlastMsg[\"content\"] = append(content, primeBlock)\n\t// \t\t\t} else if text, ok := lastMsg[\"content\"].(string); ok {\n\t// \t\t\t\tlastMsg[\"content\"] = []any{\n\t// \t\t\t\t\tmap[string]any{\"type\": \"text\", \"text\": text},\n\t// \t\t\t\t\tprimeBlock,\n\t// \t\t\t\t}\n\t// \t\t\t}\n\t// \t\t\tneedsReserialization = true\n\t// \t\t}\n\t// \t}\n\t// }\n\n\t// Prompt Caching: inject cache_control breakpoints on system + tools\n\t// Sawtooth: disabled (all 4 slots are used by CC system (2) + briefing (1) + CC messages (1))\n\t// Legacy: count existing breakpoints and fill up remaining slots.\n\tif !s.cfg.SawtoothEnabled && s.cacheGate.ShouldCache() {\n\t\tif n := InjectCacheBreakpoints(req, s.logger); n > 0 {\n\t\t\tneedsReserialization = true\n\t\t\ts.logger.Printf(\"[req %d %s] prompt cache: %d breakpoints injected\", reqIdx, proj, n)\n\t\t} else {\n\t\t\ts.logger.Printf(\"[req %d %s] prompt cache: no budget (4/4 already set)\", reqIdx, proj)\n\t\t}\n\t}\n\n\t// Final: upgrade cache TTL + enforce breakpoint limit (must run AFTER all injections + InjectCacheBreakpoints)\n\t// TTL upgrade only for API key auth — subscription (OAuth) users should not get extended TTL.\n\tif s.cfg.CacheTTL != \"\" && s.cfg.CacheTTL != \"ephemeral\" && IsAPIKeyAuth(r.Header) {\n\t\tif n := UpgradeCacheTTL(req, s.cfg.CacheTTL); n > 0 {\n\t\t\tneedsReserialization = true\n\t\t\ts.logger.Printf(\"[req %d %s] cache TTL upgraded: %d blocks → %s\", reqIdx, proj, n, s.cfg.CacheTTL)\n\t\t}\n\t}\n\tif n := EnforceCacheBreakpointLimit(req, maxCacheBreakpoints); n > 0 {\n\t\tneedsReserialization = true\n\t\ts.logger.Printf(\"[req %d %s] prompt cache: trimmed %d surplus breakpoints\", reqIdx, proj, n)\n\t}\n\n\t// Pre-flight: validate tool_use/tool_result pairing before forwarding.\n\t// Repairs orphans created by Stubify/Collapse/Injection pipeline.\n\tif msgs, ok := req[\"messages\"].([]any); ok {\n\t\trepaired, orphans := validateToolPairs(msgs, s.logger)\n\t\tif orphans > 0 {\n\t\t\treq[\"messages\"] = repaired\n\t\t\ts.logger.Printf(\"%s[req %d %s tid=%s] validate: repaired %d orphan tool_result(s)%s\", colorOrange, reqIdx, proj, threadID, orphans, colorReset)\n\t\t\tneedsReserialization = true\n\t\t}\n\t}\n\n\t// Early-out: skip re-serialization when nothing changed\n\tif !needsReserialization {\n\t\ts.logger.Printf(\"%s[req %d %s tid=%s] %d messages, ~%dk tokens (no stubbing, threshold=%dk)%s\", colorDim, reqIdx, proj, threadID, len(messages), totalTokens/1000, s.effectiveTokenThreshold(model)/1000, colorReset)\n\t\ts.forwardWithAnnotation(w, r, body, reqIdx, toolUseIDs, proj, threadID, msgCount, totalTokens)\n\t\treturn\n\t}\n\n\t// Re-serialize\n\tbody, err = json.Marshal(req)\n\tif err != nil {\n\t\ts.logger.Printf(\"%s[req %d %s tid=%s] re-marshal error: %v%s\", colorRed, reqIdx, proj, threadID, err, colorReset)\n\t\thttp.Error(w, \"internal proxy error\", http.StatusInternalServerError)\n\t\treturn\n\t}\n\n\t// Debug: dump final request body to file (enable with YESMEM_PROXY_DEBUG=1)\n\tif needsReserialization && os.Getenv(\"YESMEM_PROXY_DEBUG\") == \"1\" {\n\t\tdumpDir := filepath.Join(s.cfg.DataDir, \"debug\")\n\t\tos.MkdirAll(dumpDir, 0755)\n\t\tts := time.Now().Format(\"20060102-150405\")\n\t\tdumpPath := fmt.Sprintf(\"%s/req_%s_%d.json\", dumpDir, ts, reqIdx)\n\t\tos.WriteFile(dumpPath, body, 0644)\n\t\ts.logger.Printf(\"[req %d] dumped %dk to %s\", reqIdx, len(body)/1024, dumpPath)\n\t}\n\n\t// Task #7: Mark this request fingerprint for retry detection\n\tif !isRetryReq {\n\t\ts.markRequest(fp)\n\t}\n\n\t// Forward to Anthropic with SSE annotation extraction\n\t// Use original msgCount (pre-injection) so actual-based estimation stays consistent across turns\n\t// Use actual-based totalTokens as estimate when available (more accurate than full recount)\n\tfinalEstimate := totalTokens\n\tif s.sawtoothTrigger == nil || s.sawtoothTrigger.GetLastTokens(threadID) == 0 {\n\t\t// First request: no actual yet, do a full recount of post-injection messages\n\t\tfinalMsgs, _ := req[\"messages\"].([]any)\n\t\tfinalEstimate = s.measureOverhead(req) + s.countMessageTokens(finalMsgs)\n\t}\n\ts.logger.Printf(\"[req %d %s tid=%s] FINAL: ~%dk estimated (after all injections)\", reqIdx, proj, threadID, finalEstimate/1000)\n\tif raw := s.getRawEstimate(threadID); raw > 0 {\n\t\ts.cacheStatusWriter.UpdateRaw(raw)\n\t}\n\ts.forwardWithAnnotation(w, r, body, reqIdx, toolUseIDs, proj, threadID, msgCount, finalEstimate)\n}\n\n// bytesPerToken is the approximation factor for token estimation.\n// Anthropic recommends ~3.5 chars/token; we use 3.6 for slight overestimation\n// (triggers stubbing a bit earlier = safer for context budget).\nconst bytesPerToken = 3.6\n\n// TokenEstimateFunc is the central token estimation function.\n// All stubbing/budget decisions use this. Receives extracted text content\n// (not JSON wire format) so the tokenizer counts actual content tokens.\ntype TokenEstimateFunc func(text string) int\n"}}, "internal/setup/template_dump_test.go": {"b1ad72ef9fd508180afaef1ca46c87a17a3c675c": {"t": 1785746104.8563333, "n": 503, "h": "584ab2d389f00106", "c": "package setup\n\nimport (\n\t\"os\"\n\t\"testing\"\n)\n\nfunc TestTemplateDump_OpenCodePath(t *testing.T) {\n\tcfgStr := generateConfig(\"deepseek-v4-flash\", true, \"\", \"sk-ds-key\", \"https://api.deepseek.com/v1\",\n\t\t\"openai_compatible\", \"gnome-terminal\",\n\t\t\"deepseek-v4-pro\", \"deepseek-v4-pro\", \"deepseek-v4-flash\")\n\n\toutPath := \"/tmp/generated_opencode_config.yaml\"\n\tif err := os.WriteFile(outPath, []byte(cfgStr), 0644); err != nil {\n\t\tt.Fatal(err)\n\t}\n\tt.Logf(\"Config written to %s (%d bytes)\", outPath, len(cfgStr))\n}\n"}, "3137077a057b01485926d136fac02aaf6e32115d": {"t": 1785759336.4827845, "n": 503, "h": "584ab2d389f00106", "c": "package setup\n\nimport (\n\t\"os\"\n\t\"testing\"\n)\n\nfunc TestTemplateDump_OpenCodePath(t *testing.T) {\n\tcfgStr := generateConfig(\"deepseek-v4-flash\", true, \"\", \"sk-ds-key\", \"https://api.deepseek.com/v1\",\n\t\t\"openai_compatible\", \"gnome-terminal\",\n\t\t\"deepseek-v4-pro\", \"deepseek-v4-pro\", \"deepseek-v4-flash\")\n\n\toutPath := \"/tmp/generated_opencode_config.yaml\"\n\tif err := os.WriteFile(outPath, []byte(cfgStr), 0644); err != nil {\n\t\tt.Fatal(err)\n\t}\n\tt.Logf(\"Config written to %s (%d bytes)\", outPath, len(cfgStr))\n}\n"}}}}