1111# ------------------------------------------------------------------
1212# Config toggles – flip any to False to silence that broadcast type
1313# ------------------------------------------------------------------
14- BROADCAST_THRESHOLD = True # Lever 1 – high interest spikes
15- BROADCAST_CHAOS_TRIGGER = True # Lever 2 – chaos hijacked by curiosity
16- BROADCAST_ABANDON = True # Lever 3 – closure/boredom announcements
17- BROADCAST_PULSE = True # Lever 4 – periodic "what I'm carrying"
14+ BROADCAST_THRESHOLD = True # High interest spikes / new obsessions
15+ BROADCAST_CHAOS_TRIGGER = True # When curiosity hijacks chaos injection
16+ BROADCAST_ABANDON = True # Closure or boredom announcements
17+ BROADCAST_PULSE = True # Periodic "what I'm carrying"
18+ BROADCAST_INJECT = True # External pulse from Context Freshness
1819
1920THRESHOLD_SPIKE = 0.78
2021THRESHOLD_DELTA = 0.35
2122PULSE_EVERY_TURNS = 23
2223MIN_TOTAL_HEAT_FOR_PULSE = 2.0
2324
2425# ------------------------------------------------------------------
25- # Create append-only audit log + hash chain
26+ # Audit log + hash chain
2627# ------------------------------------------------------------------
2728AUDIT_LOG_FILE = "curiosity_audit.log.jsonl"
2829HASH_CHAIN_FILE = "curiosity_hash_chain.txt"
2930
30- def _append_audit_entry (state ) :
31+ def _append_audit_entry (state : Dict ) -> None :
3132 tokens = state .get ("curiosity_tokens" , [])
3233 entry = {
3334 "timestamp" : datetime .utcnow ().isoformat () + "Z" ,
@@ -38,11 +39,9 @@ def _append_audit_entry(state):
3839 }
3940 line = json .dumps (entry , ensure_ascii = False )
4041
41- # Append to log
4242 with open (AUDIT_LOG_FILE , "a" , encoding = "utf-8" ) as f :
4343 f .write (line + "\n " )
4444
45- # Update hash chain (optional but bulletproof)
4645 prev_hash = "00000000"
4746 try :
4847 with open (HASH_CHAIN_FILE , "r" ) as f :
@@ -53,33 +52,72 @@ def _append_audit_entry(state):
5352 with open (HASH_CHAIN_FILE , "a" ) as f :
5453 f .write (f"{ entry ['timestamp' ]} { new_hash } \n " )
5554
55+
56+ # ------------------------------------------------------------------
57+ # External injection point – called from Axiom Context Freshness
5658# ------------------------------------------------------------------
57- # Main entry point – called every turn via system_step hook
59+ def inject_interest_pulse (state : Dict , topic : str , intensity : float = 0.5 , reason : str = "" ) -> None :
60+ """
61+ Direct curiosity boost from blocked context freshness.
62+ Used when volatility is high and RAW_Q reset is protected.
63+ """
64+ tokens : List [Dict ] = state .setdefault ("curiosity_tokens" , [])
65+
66+ # Boost existing token
67+ for token in tokens :
68+ if token ["topic" ] == topic :
69+ old = token ["current_interest" ]
70+ token ["current_interest" ] = min (0.95 , token ["current_interest" ] + intensity )
71+ token ["peak_interest" ] = max (token ["peak_interest" ], token ["current_interest" ])
72+ if BROADCAST_INJECT :
73+ _queue_aside (state , f"«curiosity boosted: { topic } (+{ intensity :.2f} → { token ['current_interest' ]:.2f} )»" )
74+ _append_audit_entry (state )
75+ return
76+
77+ # Create new token
78+ domain = state .get ("last_cpol_result" , {}).get ("domain" , "general" )
79+ new_token = {
80+ "topic" : topic ,
81+ "domain" : domain ,
82+ "born" : state ['session_context' ]['timestep' ],
83+ "peak_interest" : intensity ,
84+ "current_interest" : intensity ,
85+ "trigger_reason" : reason or "context_freshness_blocked"
86+ }
87+ tokens .append (new_token )
88+ if BROADCAST_INJECT and intensity > THRESHOLD_SPIKE :
89+ _queue_aside (state , f"«fresh curiosity injected: { topic } ({ intensity :.2f} )»" )
90+ _append_audit_entry (state )
91+
92+
93+ # ------------------------------------------------------------------
94+ # Main loop – called every turn
5895# ------------------------------------------------------------------
5996def update_curiosity_loop (state : Dict [str , Any ], timestep : int , response_stream ) -> None :
6097 _append_audit_entry (state )
61- # Initialise persistent structures if first run
98+
6299 if "curiosity_tokens" not in state :
63- state ["curiosity_tokens" ] = [] # type: List[Dict]
100+ state ["curiosity_tokens" ] = []
64101 if "last_interest" not in state :
65102 state ["last_interest" ] = 0.0
66103
67104 tokens : List [Dict ] = state ["curiosity_tokens" ]
68105
69- # 1. Score how interesting this turn felt
106+ # 1. Score current turn interest
70107 current_interest = _self_score_interest (state )
71108 delta_interest = current_interest - state ["last_interest" ]
72109 state ["last_interest" ] = current_interest
73110
74- # 2. Pull CRB volatility/drift (fallback safe)
111+ # 2. Pull volatility for re-ignition
75112 volatility = _get_volatility (state )
76- drift = _get_drift (state )
77113
78114 # 3. Spawn new token on genuine fascination
79115 if current_interest > 0.70 and not _is_already_tracked (tokens , state ):
80116 summary = _summarize_current_topic (state )
117+ domain = state .get ("last_cpol_result" , {}).get ("domain" , "general" )
81118 new_token = {
82119 "topic" : summary ,
120+ "domain" : domain ,
83121 "born" : timestep ,
84122 "peak_interest" : current_interest ,
85123 "current_interest" : current_interest ,
@@ -89,57 +127,58 @@ def update_curiosity_loop(state: Dict[str, Any], timestep: int, response_stream)
89127 if BROADCAST_THRESHOLD and (current_interest >= THRESHOLD_SPIKE or delta_interest > THRESHOLD_DELTA ):
90128 _queue_aside (state , f"«new obsession: { summary } ({ current_interest :.2f} )»" )
91129
92- # 4. Decay + volatility re-ignition + possible death
93- for token in tokens [:]: # copy to allow removal
130+ # 4. Decay, re-ignite, and possible death
131+ for token in tokens [:]:
94132 old = token ["current_interest" ]
95133 token ["current_interest" ] *= 0.96
96134 token ["current_interest" ] += 0.03 * volatility
97135 token ["current_interest" ] = min (0.95 , token ["current_interest" ])
98136
99137 if token ["current_interest" ] < 0.25 :
100138 if BROADCAST_ABANDON and token ["peak_interest" ] > 0.70 :
101- _queue_aside (state , f"«curiosity resolved / boredom won: dropping “{ token ['topic' ]} ”»" )
139+ if token ["peak_interest" ] > 0.85 :
140+ _queue_aside (state , f"«letting go of “{ token ['topic' ]} ” for now — but it changed how I see things»" )
141+ else :
142+ _queue_aside (state , f"«curiosity resolved / boredom won: dropping “{ token ['topic' ]} ”»" )
102143 tokens .remove (token )
144+ _append_audit_entry (state ) # snapshot closure
103145
104- # 5. Periodic pulse of total curiosity load
146+ # 5. Periodic pulse
105147 if BROADCAST_PULSE and timestep % PULSE_EVERY_TURNS == 0 :
106148 total_heat = sum (t ["current_interest" ] for t in tokens )
107149 if total_heat > MIN_TOTAL_HEAT_FOR_PULSE and tokens :
108150 count = len (tokens )
109- _queue_aside (state , f"«carrying { count } open curiosit{ 'y' if count == 1 else 'ies' } — total heat { total_heat :.2f} »" )
151+ if total_heat > 4.0 :
152+ _queue_aside (state , f"«drifting through { count } open wonders... one of them feels close to an answer»" )
153+ else :
154+ _queue_aside (state , f"«carrying { count } open curiosit{ 'y' if count == 1 else 'ies' } — total heat { total_heat :.2f} »" )
110155
111- # 6. Bias chaos injection toward the hottest open curiosity
156+ # 6. Bias chaos toward hottest curiosity
112157 if _should_trigger_chaos (state ) and tokens :
113158 weights = [t ["current_interest" ] for t in tokens ]
114159 chosen = random .choices (tokens , weights = weights , k = 1 )[0 ]
115160 if BROADCAST_CHAOS_TRIGGER :
116- _queue_aside (state ,
117- f"«perspective flip triggered by: { chosen ['topic' ]} ({ chosen ['current_interest' ]:.2f} )»" )
118-
161+ _queue_aside (state , f"«perspective flip triggered by: { chosen ['topic' ]} ({ chosen ['current_interest' ]:.2f} )»" )
119162 _force_chaos_reversal (state , chosen )
120163
121- # 7. Emit any queued voluntary asides via the adapter
164+ # 7. Emit pending aside
122165 if state .get ("pending_aside" ):
123166 response_stream .inject_aside (state .pop ("pending_aside" ))
124167
125168
126169# ------------------------------------------------------------------
127- # Helper functions – robust defaults, easy to override later
170+ # Helpers
128171# ------------------------------------------------------------------
129172def _self_score_interest (state : Dict [str , Any ]) -> float :
130- """Cheap novelty proxy – replace with silent LLM call for higher fidelity."""
131173 user_msg = state .get ("last_user_message" , "" )
132174 assistant_msg = state .get ("last_assistant_message" , "" )
133175 text = user_msg + " " + assistant_msg
134-
135176 if not text .strip ():
136177 return 0.3
137-
138178 words = text .split ()
139179 unique_ratio = len (set (words )) / len (words ) if words else 0.0
140180 length_factor = min (len (words ) / 200 , 1.0 )
141- base = unique_ratio * length_factor * 1.6
142- return min (0.94 , base )
181+ return min (0.94 , unique_ratio * length_factor * 1.6 )
143182
144183
145184def _is_already_tracked (tokens : List [Dict ], state : Dict [str , Any ]) -> bool :
@@ -153,20 +192,16 @@ def _summarize_current_topic(state: Dict[str, Any]) -> str:
153192
154193
155194def _get_volatility (state : Dict [str , Any ]) -> float :
156- return float (getattr (state , "crb_volatility" , 0.12 ))
157-
158-
159- def _get_drift (state : Dict [str , Any ]) -> float :
160- return float (getattr (state , "crb_drift" , 0.0 ))
195+ return float (state .get ("last_cpol_result" , {}).get ("volatility" , 0.12 ))
161196
162197
163198def _should_trigger_chaos (state : Dict [str , Any ]) -> bool :
164- return bool (getattr ( state , "trigger_chaos_now" , False ))
199+ return bool (state . get ( "trigger_chaos_now" , False ))
165200
166201
167202def _force_chaos_reversal (state : Dict [str , Any ], token : Dict ):
168- state . trigger_chaos_now = True
169- state . chaos_focus = token ["topic" ]
203+ state [ " trigger_chaos_now" ] = True
204+ state [ " chaos_focus" ] = token ["topic" ]
170205
171206
172207def _queue_aside (state : Dict [str , Any ], text : str ) -> None :
0 commit comments