Skip to content

Prompt + reply · how each is built

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