Skip to content

Turn loop re-posts stale prior reply when a turn fails without an error tag (repeating-reply bug) #38

Description

@nanparth

Symptom

  • An agent re-posts its previous session's reply verbatim on every turn-triggering message; no real turns run.
  • Registry-only slash commands (/help, unknown-command note) keep working — only turn-sending input loops.
  • Observed on a simmis dev instance (dvergr checkout + spindel 0.1.35).

Evidence

  • "Replies" land 0.4–0.8 s after the human message — no LLM latency, so no generation happened.
  • Each replay is stored with a fresh message id, so -store-message!'s id-idempotency can't dedupe; the room store accumulated 4 copies of the same reply.
  • Zero post-turn-error! ("⚠️ turn failed") rows in the room — the stale-reply guard never fired.
  • (providers/list-providers) was [] at the time (see trigger below).

Root cause

  1. Trigger (config, my side): the agent's provider (:fireworks) was never registered — no *_API_KEY env vars, so init-defaults! registered nothing. Every turn failed immediately at provider resolution. (The boot-time :providers/none-registered warn fired, but is easy to miss.)
  2. Bug (dvergr): the failure ends the turn with no gen/error-result? tag, so errored stays nil (src/dvergr/discourse/llm.clj:488 on main) and the loop falls through to llm.clj:527, posting last-assistant-message of the chat-ctx. On turn 0 that ctx was just seeded from the store, so "last assistant message" is the previous session's reply — re-posted as a new message, every time. The post-turn-error! guard (src/dvergr/agent/turn.clj:188, added for exactly this "repeating bug") only covers error-tagged results at llm.clj:526.

Repro

  1. Agent whose provider isn't registered (e.g. clear providers), in a room with existing history.
  2. Send any plain message → the stale prior reply is re-posted in under a second. Repeats on every message.

Suggested fix

  • Gate the final reply-post on "this invocation produced a new assistant message" (e.g. compare the chat-ctx assistant-message count before/after the turn loop) instead of only on errored — that closes every silent-failure shape, not just this one.
  • Additionally, surface provider-missing as a proper error-result so post-turn-error! reports it in-room instead of only the boot-time warn.

Side effect worth knowing

Replays permanently pollute the room store (fresh ids defeat idempotency), so affected agents carry duplicated context even after the fix.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions