aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--AGENTS.md3
-rw-r--r--assistant.py21
-rw-r--r--docs/superpowers/specs/2026-10-23-cloud-models-design.md4
3 files changed, 20 insertions, 8 deletions
diff --git a/AGENTS.md b/AGENTS.md
index a7cbc46..645fc87 100644
--- a/AGENTS.md
+++ b/AGENTS.md
@@ -297,7 +297,8 @@ and history were untouched.
- **Hetzner sends `usage` on every chunk**, not once at the end: the
daemon sent ~60 `ctx` events per reply. Now one per request.
- Titles from the cloud took ~12 s: `enable_thinking` is not sent to
- providers, so Qwen reasons over the title too. Harmless, after done.
+ providers, so Qwen reasoned over the title too. Titles now always come
+ from the local loaded model, thinking off.
- Qwen's replies start with a blank line or two after its reasoning;
Markdown drops them.
- The key appeared in no log line.
diff --git a/assistant.py b/assistant.py
index fac6b9c..2bc91d5 100644
--- a/assistant.py
+++ b/assistant.py
@@ -1211,18 +1211,19 @@ def answer(mine, text, files=(), chosen=None):
emit({"type": "state", "state": "idle"})
log("assistant:", said)
if fresh:
- retitle(mine, conv, text, said, model)
+ retitle(mine, conv, text, said)
-def retitle(mine, conv, user, said, model=None):
+def retitle(mine, conv, user, said):
# The first six words stand until the model names the conversation. Asked
# after done, under the reply's turn: the next ask, stop, new or open
# hangs it up, so it never holds a reply back, and the six words stay.
+ # Always the local model, cloud chat or not: with thinking off it is
+ # quick, where a cloud model reasoned over the title for ~12 s.
try:
msgs = [{"role": "user",
"content": f"{TITLE}\n\nUser: {user[:2000]}\n\nAssistant: {said[:2000]}"}]
- text = "".join(p for k, p in ask(msgs, {}, None, mine, think=False, meter=False,
- model=model)
+ text = "".join(p for k, p in ask(msgs, {}, None, mine, think=False, meter=False)
if k == "token")
line = next((l for l in text.splitlines() if l.strip()), "")
title = line.strip().strip("\"'*#").strip()[:80]
@@ -1515,7 +1516,8 @@ def test():
pass
def do_GET(self):
- script["auth"] = self.headers.get("Authorization")
+ if self.headers.get("Authorization"): # cloud_models; the router's has none
+ script["auth"] = self.headers["Authorization"]
body = ({"data": [{"id": "m", "status": {"value": "loaded"},
"architecture": {"input_modalities": script["inputs"]}}]}
if self.path.endswith("/models")
@@ -1525,9 +1527,9 @@ def test():
self.wfile.write(json.dumps(body).encode())
def do_POST(self):
- script["auth"] = self.headers.get("Authorization")
body = json.loads(self.rfile.read(int(self.headers["Content-Length"])))
if str(body["messages"][0]["content"]).startswith(TITLE):
+ script["title_path"] = self.path
# Answered apart, so a title never takes a reply meant for an ask.
self.send_response(200)
self.end_headers()
@@ -1535,6 +1537,7 @@ def test():
"content": script.get("title", "")}}]}).encode() + b"\n\ndata: [DONE]\n\n")
return
script.get("paths", []).append(self.path)
+ script["auth"] = self.headers.get("Authorization") # not a title's
script["body"] = body
script.get("bodies", []).append(script["body"])
if script.get("hold"):
@@ -1872,8 +1875,14 @@ def test():
{"tool_calls": [{"index": 0, "id": "k1", "function": {"name": "mail",
"arguments": "{}"}}]}],
[{"content": "No mail tool."}]]
+ script["title_path"] = None
send(type="ask", text="any mail?", model="fake:vendor/x:free")
seen = until("done")
+ for _ in range(40): # the title comes after done
+ if script["title_path"]:
+ break
+ time.sleep(0.05)
+ assert script["title_path"] == "/v1/chat/completions" # local, not the provider
first, second = script["bodies"]
assert script["paths"] == ["/cloud/v1/chat/completions"] * 2
assert script["auth"] == "Bearer sk-test"
diff --git a/docs/superpowers/specs/2026-10-23-cloud-models-design.md b/docs/superpowers/specs/2026-10-23-cloud-models-design.md
index 5cf2c56..d0e91b7 100644
--- a/docs/superpowers/specs/2026-10-23-cloud-models-design.md
+++ b/docs/superpowers/specs/2026-10-23-cloud-models-design.md
@@ -85,7 +85,9 @@ ids contain `/` and sometimes `:` (OpenRouter's `:free`).
the chat's model. The audio never leaves; only the transcript does, as
the text question. If the loaded model cannot hear, the ask is refused
as today.
-- **Titles.** Written by the chat's own model.
+- **Titles.** Written by the local loaded model, thinking off, whatever
+ the chat's model (changed after the live check: a cloud model reasoned
+ over the title for ~12 s).
- **To .md** (`write`) has no chat, so it always uses the local loaded
model.
- **`models` command.** Answers with the router's models (id, status)