Files

1 line
1.5 MiB
Plaintext
Raw Permalink Blame History

This file contains invisible Unicode characters
This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
{"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:<model>)\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\": \"<channel source=\\\"yesmem-dialog\\\">\\n\" + dr.Extra + \"\\n</channel>\",\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:<model>)\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\": \"<channel source=\\\"yesmem-dialog\\\">\\n\" + dr.Extra + \"\\n</channel>\",\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:<model>)\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\": \"<channel source=\\\"yesmem-dialog\\\">\\n\" + dr.Extra + \"\\n</channel>\",\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:<model>)\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\": \"<channel source=\\\"yesmem-dialog\\\">\\n\" + dr.Extra + \"\\n</channel>\",\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:<model>)\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\": \"<channel source=\\\"yesmem-dialog\\\">\\n\" + dr.Extra + \"\\n</channel>\",\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:<model>)\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\": \"<channel source=\\\"yesmem-dialog\\\">\\n\" + dr.Extra + \"\\n</channel>\",\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:<model>)\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\": \"<channel source=\\\"yesmem-dialog\\\">\\n\" + dr.Extra + \"\\n</channel>\",\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\": \"<channel source=\\\"yesmem-dialog\\\">\\n\" + dr.Extra + \"\\n</channel>\",\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\": \"<channel source=\\\"yesmem-dialog\\\">\\n\" + dr.Extra + \"\\n</channel>\",\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\": \"<channel source=\\\"yesmem-dialog\\\">\\n\" + dr.Extra + \"\\n</channel>\",\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\": \"<channel source=\\\"yesmem-dialog\\\">\\n\" + dr.Extra + \"\\n</channel>\",\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\": \"<channel source=\\\"yesmem-dialog\\\">\\n\" + dr.Extra + \"\\n</channel>\",\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\": \"<channel source=\\\"yesmem-dialog\\\">\\n\" + dr.Extra + \"\\n</channel>\",\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\": \"<channel source=\\\"yesmem-dialog\\\">\\n\" + dr.Extra + \"\\n</channel>\",\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\": \"<channel source=\\\"yesmem-dialog\\\">\\n\" + dr.Extra + \"\\n</channel>\",\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\": \"<channel source=\\\"yesmem-dialog\\\">\\n\" + dr.Extra + \"\\n</channel>\",\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\": \"<channel source=\\\"yesmem-dialog\\\">\\n\" + dr.Extra + \"\\n</channel>\",\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\": \"<channel source=\\\"yesmem-dialog\\\">\\n\" + dr.Extra + \"\\n</channel>\",\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\": \"<channel source=\\\"yesmem-dialog\\\">\\n\" + dr.Extra + \"\\n</channel>\",\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\": \"<channel source=\\\"yesmem-dialog\\\">\\n\" + dr.Extra + \"\\n</channel>\",\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\": \"<channel source=\\\"yesmem-dialog\\\">\\n\" + dr.Extra + \"\\n</channel>\",\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\": \"<channel source=\\\"yesmem-dialog\\\">\\n\" + dr.Extra + \"\\n</channel>\",\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"}}}}