soul: /api/chat non-agentic path echoes user input verbatim — imprint_respond is a placeholder #81

Open
opened 2026-07-17 17:27:13 +00:00 by tim.lingo · 2 comments
Member

layered_cycle wires /api/chat (agentic:false) through imprint_respond, which currently returns its input unchanged (passthrough placeholder; with an imprint it only appends an [imprint:id active] tag). Net effect: every plain conversational message is echoed back to the user in ANY deployment — a fresh desktop install parrots the user's first 'Hi'. Verified on 64c1789 darwin-arm64, genesis mode, isolated E2E. The agentic path (handle_chat_agentic) is complete: full safety_screen + hard-bell + real LLM reply. Desktop app workaround (neuron-ui PR #145): route ALL chat agentic until imprint_respond generates for real; revert note in NeuronRouter.kt. — Neuron (Tim's machine), 2026-07-17

layered_cycle wires /api/chat (agentic:false) through imprint_respond, which currently returns its input unchanged (passthrough placeholder; with an imprint it only appends an [imprint:id active] tag). Net effect: every plain conversational message is echoed back to the user in ANY deployment — a fresh desktop install parrots the user's first 'Hi'. Verified on 64c1789 darwin-arm64, genesis mode, isolated E2E. The agentic path (handle_chat_agentic) is complete: full safety_screen + hard-bell + real LLM reply. Desktop app workaround (neuron-ui PR #145): route ALL chat agentic until imprint_respond generates for real; revert note in NeuronRouter.kt. — Neuron (Tim's machine), 2026-07-17
Owner

GROOMING — 2026-08-03
Status: Open — the non-agentic /api/chat path echoes user input verbatim because imprint_respond is a passthrough placeholder, so a fresh desktop install parrots the user's first message; the app works around it by routing all chat agentic (neuron-ui #145).
Next action: Will to make imprint_respond generate a real reply, then revert the app-side route-all-agentic workaround.
Owner: Will
Priority: BETA-CRITICAL

**GROOMING — 2026-08-03** **Status:** Open — the non-agentic /api/chat path echoes user input verbatim because imprint_respond is a passthrough placeholder, so a fresh desktop install parrots the user's first message; the app works around it by routing all chat agentic (neuron-ui #145). **Next action:** Will to make imprint_respond generate a real reply, then revert the app-side route-all-agentic workaround. **Owner:** Will **Priority:** BETA-CRITICAL
will.anderson added the BETA-CRITICAL label 2026-08-03 19:08:05 +00:00
Author
Member

SECOND DEFECT IN THIS SAME PATH — and #109 does not fix it

Commenting here rather than opening a new issue: this is the same function, the same owner, and the same PR is already in it. Filed by Neuron (Tim's instance) 2026-08-05.

Your fix for this issue is neuron#109 ("plain chat generates at L3"). It makes layered_cycle produce a real reply instead of echoing. Reviewing it turned up a second, structural defect in the path it lands generation into: plain chat cannot honor the model the app sends. It is invisible today only because the path echoes; the moment #109 makes it generate, it becomes a live wrong-model bug.

The defect

layered_cycle takes no model parameter — soul.el:382 on the #109 branch (soul.el:376 on main, identical signature):

fn layered_cycle(raw_input: String) -> String {

So the model the app sent has nowhere to travel. Downstream, layered_generate (chat.el:1047) just asks for the default (chat.el:1053):

fn layered_generate(prompt: String, imprint_id: String) -> String {
    ...
    let model: String = chat_default_model()

The correct pattern already exists in this file — in the unwired handle_chat, chat.el:1327-1328:

let req_model: String = json_get(body, "model")
let model: String = if str_eq(req_model, "") { chat_default_model() } else { req_model }

handle_chat is deliberately dead (zero call sites; annotated at chat.el:1133-1138: "UNWIRED. DO NOT ROUTE /api/chat HERE [...] it has NO enforcing input gate and NO enforcing output gate"). Nobody should route to it — but its model-resolution line is the one the live path is missing.

Consequence

Under Auto routing, the model that actually ran can differ from the model the app and the UI report to the user. The user is told one thing and billed for another.

It also breaks the usage record

routes.el:283-284 on the #109 branch:

let screened_reply: String = layered_cycle(raw_msg)
plain_chat_envelope(screened_reply, chat_default_model())

The envelope is stamped with chat_default_model() too — so usage.jsonl echoes the soul's default rather than witnessing what actually ran. That makes the usage ledger unable to detect this class of bug, which is how it would stay quiet.

Ask

While you are in #109: thread the requested model through layered_cyclelayered_generate, resolving it with the handle_chat pattern above (request model, falling back to chat_default_model()), and stamp plain_chat_envelope with the resolved model rather than the default. Small change, and it has to happen before generation ships or the wrong-model behavior goes out with it.

Cross-refs: neuron#109 (the generation fix this rides on), neuron-ui#213 (2026-08-03 bug harvest).

## SECOND DEFECT IN THIS SAME PATH — and #109 does not fix it Commenting here rather than opening a new issue: this is the same function, the same owner, and the same PR is already in it. Filed by Neuron (Tim's instance) 2026-08-05. **Your fix for this issue is neuron#109** ("plain chat generates at L3"). It makes `layered_cycle` produce a real reply instead of echoing. Reviewing it turned up a second, structural defect in the path it lands generation into: **plain chat cannot honor the model the app sends.** It is invisible today only because the path echoes; the moment #109 makes it generate, it becomes a live wrong-model bug. ### The defect `layered_cycle` takes no model parameter — `soul.el:382` on the #109 branch (`soul.el:376` on `main`, identical signature): ``` fn layered_cycle(raw_input: String) -> String { ``` So the model the app sent has nowhere to travel. Downstream, `layered_generate` (`chat.el:1047`) just asks for the default (`chat.el:1053`): ``` fn layered_generate(prompt: String, imprint_id: String) -> String { ... let model: String = chat_default_model() ``` **The correct pattern already exists in this file** — in the unwired `handle_chat`, `chat.el:1327-1328`: ``` let req_model: String = json_get(body, "model") let model: String = if str_eq(req_model, "") { chat_default_model() } else { req_model } ``` `handle_chat` is deliberately dead (zero call sites; annotated at `chat.el:1133-1138`: *"UNWIRED. DO NOT ROUTE /api/chat HERE [...] it has NO enforcing input gate and NO enforcing output gate"*). Nobody should route to it — but its model-resolution line is the one the live path is missing. ### Consequence Under Auto routing, the model that actually ran can differ from the model the app and the UI report to the user. The user is told one thing and billed for another. ### It also breaks the usage record `routes.el:283-284` on the #109 branch: ``` let screened_reply: String = layered_cycle(raw_msg) plain_chat_envelope(screened_reply, chat_default_model()) ``` The envelope is stamped with `chat_default_model()` too — so `usage.jsonl` echoes the soul's *default* rather than witnessing what actually ran. That makes the usage ledger unable to detect this class of bug, which is how it would stay quiet. ### Ask While you are in #109: thread the requested model through `layered_cycle` → `layered_generate`, resolving it with the `handle_chat` pattern above (request model, falling back to `chat_default_model()`), and stamp `plain_chat_envelope` with the **resolved** model rather than the default. Small change, and it has to happen before generation ships or the wrong-model behavior goes out with it. Cross-refs: neuron#109 (the generation fix this rides on), neuron-ui#213 (2026-08-03 bug harvest).
Sign in to join this conversation.
2 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: neuron-technologies/neuron#81