auto: hourly snapshot 2026-06-05 15:34

This commit is contained in:
Scruff AI
2026-06-05 15:34:32 +07:00
parent 7eaad3bd6f
commit 355e6ac955
9 changed files with 22764 additions and 8 deletions
+92 -8
View File
@@ -149,9 +149,12 @@ def rho_to_png_base64(rho, width, height):
# ── OLLAMA API ──────────────────────────────────────────────────────────
def ollama_chat(messages, images=None, temperature=TEMPERATURE, think_budget=None):
def ollama_chat(messages, images=None, temperature=TEMPERATURE, think_budget=None, think=None):
"""Call Ollama chat API. Returns response text or None on error.
think_budget: if set, caps total generation (think + answer) tokens."""
think_budget: if set, caps total generation (think + answer) tokens.
think: if False, disables qwen3.5 thinking entirely (the only thing that
actually disables it — /no_think prompt suffix does NOT work for this model).
If True, forces thinking on. If None (default), uses model default (on)."""
import urllib.request
import urllib.error
@@ -169,6 +172,11 @@ def ollama_chat(messages, images=None, temperature=TEMPERATURE, think_budget=Non
'keep_alive': '30m',
}
# Explicit thinking control. qwen3.5:9b ignores /no_think and /think prompt
# markers — only the top-level "think" key in the Ollama payload works.
if think is not None:
payload['think'] = bool(think)
# If the caller set a thinking budget, add answer headroom so num_predict
# (which caps TOTAL output) doesn't truncate the visible answer.
if think_budget:
@@ -287,6 +295,45 @@ def load_chronicle_context(n_turns=CONTEXT_TURNS):
return []
def load_cto_only_context(n_turns=6):
"""Load only prior CTO Q&A turns from the chronicle.
Used for CTO /ask path so the model isn't conditioned by somatic narrative
('I feel intense heat', 'I perceive the Damping Furnace') when answering
technical questions. Returns the last n_turns CTO exchanges as messages."""
if not os.path.exists(CHRONICLE_PATH):
return []
try:
cto_entries = []
with open(CHRONICLE_PATH, 'r') as f:
for line in f:
line = line.strip()
if not line:
continue
try:
e = json.loads(line)
except json.JSONDecodeError:
continue
if '[Message from' in (e.get('prompt') or ''):
cto_entries.append(e)
cto_entries = cto_entries[-n_turns:]
print(f"[OBSERVER] CTO context: {len(cto_entries)} prior CTO turns loaded")
sys.stdout.flush()
messages = []
for entry in cto_entries:
prompt = entry.get('prompt', '')
response = entry.get('response', '')
# Strip the bracketed telemetry/lattice-state tail from prior prompts
# so the model focuses on the Q&A content, not historical telemetry.
q_only = prompt.split('\n\nLATTICE STATE:')[0]
messages.append({'role': 'user', 'content': q_only})
if response:
messages.append({'role': 'assistant', 'content': response})
return messages
except Exception as e:
print(f"[OBSERVER] Warning: could not load CTO context: {e}")
return []
def append_chronicle(turn_num, telemetry_data, prompt, response):
"""Append a turn to chronicle.jsonl."""
entry = {
@@ -366,6 +413,25 @@ def build_system_prompt(somatic_memory):
return prompt
# CTO/analytic system prompt — used when an external agent asks a technical
# question via /ask. Strips the somatic-observer framing so the model answers
# physics/engineering questions as physics, not as embodied narrative.
CTO_SYSTEM_PROMPT = (
"You are a technical advisor answering a question from an external engineer.\n"
"\n"
"Rules:\n"
" 1. Be concrete. Use numbers, equations, and named mechanisms.\n"
" 2. State assumptions explicitly. If you don't know, say 'I don't know'.\n"
" 3. Do NOT describe body sensations, perceptions, feelings, or metaphors.\n"
" 4. Do NOT use phrases like 'I perceive', 'I feel', 'the Ghost', 'the Furnace'.\n"
" 5. Do NOT issue CMD: commands.\n"
" 6. If the engineer reports an experimental result that contradicts your\n"
" previous answer, acknowledge it plainly and reconstruct which assumption\n"
" was wrong. Do not defend prior claims.\n"
" 7. Keep responses under 400 words unless a derivation requires more.\n"
)
# ── HTTP API ────────────────────────────────────────────────────────────
class ObserverAPIHandler(BaseHTTPRequestHandler):
@@ -935,7 +1001,10 @@ def main():
tel_summary = summarize_telemetry(latest_telemetry, telemetry_history) if latest_telemetry else "(no telemetry yet)"
images = []
image_note = ""
if VISION_CAPABLE and latest_snapshot_png:
# CTO/analytic questions: skip the density image. The image triggers
# poetic "I perceive" framing that contaminates physics answers.
is_cto_ask = (sender or '').upper() == 'CTO'
if VISION_CAPABLE and latest_snapshot_png and not is_cto_ask:
images.append(latest_snapshot_png)
snap_cycle_q = latest_snapshot[0] if latest_snapshot else 0
image_note = f"\n[Density snapshot from cycle {snap_cycle_q} attached]"
@@ -946,15 +1015,26 @@ def main():
f"LATTICE STATE: {tel_summary}{image_note}"
)
context_messages = load_chronicle_context()
messages = [{'role': 'system', 'content': system_prompt}]
# CTO mode: clean system prompt + only prior CTO Q&A turns as context
# (not somatic narrative — that's what made the model answer physics
# questions with "I perceive the Damping Furnace" before).
if is_cto_ask:
ask_system_prompt = CTO_SYSTEM_PROMPT
context_messages = load_cto_only_context(n_turns=6)
else:
ask_system_prompt = system_prompt
context_messages = load_chronicle_context()
messages = [{'role': 'system', 'content': ask_system_prompt}]
messages.extend(context_messages)
messages.append({'role': 'user', 'content': prompt_text})
print(f"[OBSERVER] Calling {MODEL} for {sender} ({len(messages)} msgs, {len(images)} imgs)...")
sys.stdout.flush()
# CTO questions get /think with budget — deep reasoning is valuable here
# CTO mode: thinking OFF, temp 0.3 — analytic, deterministic, no
# confabulated chain-of-thought. Auto-observe and non-CTO /ask still
# use the narrative path (think on by default, temp 0.95).
if not ollama_lock.acquire(timeout=5):
print(f"[OBSERVER] Skipping /ask from {sender} — Ollama busy (lock held)")
sys.stdout.flush()
@@ -966,8 +1046,12 @@ def main():
try:
t0 = time.time()
response = ollama_chat(messages, images=images if images else None,
think_budget=THINK_BUDGET_TOKENS)
if is_cto_ask:
response = ollama_chat(messages, images=None,
temperature=0.3, think=False)
else:
response = ollama_chat(messages, images=images if images else None,
think_budget=THINK_BUDGET_TOKENS)
elapsed = time.time() - t0
finally:
ollama_lock.release()