flowchart TD
NOTE["How each turn's PROMPT is assembled and how the REPLY is built + kept honest.<br/>TOP = everything that gets packed INTO the main chat LLM. BOTTOM = how the model's output becomes the user's reply.<br/>Dotted = a source feeding a block. Optional blocks load only when relevant. Before the LLM even runs, an honest-refusal short-circuit can fire."]
UMSG(["User message + channel"])
UMSG --> PRE
PRE{"did the router leave an honest-refusal marker?<br/>(it recognized a write it cannot route, or its transport failed)"}
PRE -->|marker set| HON["HONEST REFUSAL · RFC 0105 · _mutation_denied_message<br/>SKIPS the LLM entirely · says what it recognized and that it cannot do it yet · never fabricates"]
PRE -->|clear| SYS
subgraph SYS["A · SYSTEM PROMPT · set once per turn"]
direction TB
SP1["name preamble — 'You are NAME.' · memlink.get_agent_name()"]
SP2["base system prompt — cfg.base_system_prompt · the platform hard-rules (PRD 0032)"]
SP3["operator override — memlink.get_agent_system_prompt() living-doc · editable · currently empty"]
SP4["channel rendering — markdown for the web · plain text for Telegram"]
SP1 --> SP2 --> SP3 --> SP4
end
subgraph UWRAP["B · USER-MESSAGE WRAPPER · ordered context blocks"]
direction TB
U1["date + time in the owner's timezone"]
U2["runtime guidance — memlink.assemble_runtime_context() · DB-backed operator edits"]
U6["capability ground-truth — _capabilities_grounded_summary() from the tool registry (ADR 0178)<br/>never deny a listed tool · never claim an action it did not actually run"]
U7["retrieved memory evidence — memlink.retrieve_answer_context() · facts, relations, dossier<br/>gated by retrieval_enabled · treated as DATA only (INV-022 rule 3)"]
U8["memory-unavailable notice — if the store cannot be reached, say so honestly, do not guess"]
U9["identity guidance — loaded only for identity questions"]
UOPT["optional hints — timezone re-phrase · code-introspection results · self-code-mode directive"]
RAW["Current user message: the raw text"]
U1 --> U2 --> U6 --> U7 --> U8 --> U9 --> UOPT --> RAW
end
SYS ~~~ UWRAP
MEM[("Memlink · Postgres · world.* + steelmoth.*")]
REG[("tool registry · ToolSpec.capability_phrase")]
HIST[("history cache · _history_by_chat · recent turns")]
MEM -. agent name + identity .-> SP1
MEM -. system-prompt override .-> SP3
MEM -. runtime guidance .-> U2
MEM -. memory recall .-> U7
REG -. capability phrases .-> U6
MSGS["MESSAGE LIST · _build_messages()<br/>system(system prompt) + conversation history + user(wrapped message)"]
SYS --> MSGS
UWRAP --> MSGS
HIST -. prior turns .-> MSGS
LLM["MAIN CHAT LLM · _generate_web_reply_async · source=web_chat_assistant_reply<br/>provider chain, first success wins — openai / codex → minimax / MiniMax-M3 → glm → deepseek<br/>streams tokens + reasoning where the provider supports it"]
MSGS --> LLM
USAGE[("steelmoth.llm_usage_event")]
LLM -. tokens + latency logged .-> USAGE
subgraph REPLY["REPLY CONSTRUCTION · _run_chat_reply_pipeline + streaming"]
direction TB
STREAM["streaming events to the browser (SSE)<br/>MessageStart · PlannerDecision · ReasoningDelta = thinking · TokenDelta = visible text · MessageComplete"]
GATE1["anti-fabrication gate · RFC 0105 · fires only when NO tool ran this turn<br/>if the model claims it did an action (sent, captured, deleted), rewrite the reply to be honest"]
GATE2["follow-up honesty gate · RFC 0071 · catches 'I will let you know' promises<br/>emits a Workspace-Inbox chip instead of a fake promise · does NOT rewrite the text"]
HINTS["action hints + chips · AssistantActionHint — approval chips · follow-up chip · sources · attachments"]
STREAM --> GATE1 --> GATE2 --> HINTS
end
LLM --> STREAM
PERSIST["PERSIST · persist_web_assistant_reply → memlink.ingest_event<br/>and append the reply to the history cache for the next turn"]
TURNS[("steelmoth.chat_turns · chat_message_reasoning · chat_message_hints")]
OUTUSER(["reply streamed to the user · SSE"])
HINTS --> OUTUSER
HINTS --> PERSIST
HON --> OUTUSER
HON -. also saved .-> PERSIST
PERSIST --> TURNS
style SYS fill:#ecfeff,stroke:#0c8ea4,stroke-width:2px,color:#0c8ea4
style UWRAP fill:#fffaf0,stroke:#b45309,stroke-width:2px,color:#b45309
style REPLY fill:#ecfdf3,stroke:#1f9d63,stroke-width:3px,color:#1f9d63
classDef blN fill:#ffffff,stroke:#2f6df6,color:#27272a,stroke-width:1px
classDef tlN fill:#ffffff,stroke:#0c8ea4,color:#27272a,stroke-width:1px
classDef goN fill:#ffffff,stroke:#b45309,color:#27272a,stroke-width:1px
classDef gnN fill:#ffffff,stroke:#1f9d63,color:#27272a,stroke-width:1px
classDef amN fill:#ffffff,stroke:#c2570c,color:#27272a,stroke-width:1px
classDef rdN fill:#f1f5f9,stroke:#e5484d,color:#27272a,stroke-width:2px
classDef slN fill:#ffffff,stroke:#475569,color:#27272a,stroke-width:1px
class NOTE,MEM,REG,HIST,USAGE,TURNS,UOPT,U8 slN
class SP1,SP2,SP3,SP4 tlN
class U1,U2,U6,U7,U9,RAW goN
class MSGS,LLM,PRE,UMSG blN
class STREAM,PERSIST,OUTUSER,GATE1 gnN
class GATE2,HINTS amN
class HON rdN