Nodes/ComfyUI llama.cpp Suite/llama.cpp Append Message
ComfyUI Node

llama.cpp Append Message

Real conversation history, with no hidden state to lose

By Setmaster·Created 9 months ago·Updated 3 days ago· 9
llama.cpp Append Message
  • messages
  • messages
  • messages_json
◄roleuser►
◄content—►

Why a node for one message

Because chat memory is where ComfyUI LLM workflows quietly stop being reproducible. Plenty of "LLM node" packs keep a conversation on the server or in a Python dict, which means the graph you saved is not the graph that runs next Tuesday, and the same workflow gives you different text on a rerun for reasons you can't see.

Append Message takes the opposite approach: history is a value that flows along a wire. Give it a role and some content, optionally feed it an earlier messages, and it hands back a new, longer history. The input history isn't mutated - you get a fresh immutable value. Chain two of them and you've got a three-turn conversation made of literal text sitting in your graph, saved with the workflow, reviewable by a human, diffable in git.

That's it. That's the node. It's deliberately boring, and the boringness is the feature.

How it fits with Generate

llama.cpp Generate has an optional messages input for earlier turns. It sends those turns before its current prompt and appends the current prompt as the newest user turn. Nothing is added automatically: if you don't wire history, there isn't any.

The reason to bother is few-shot. A chat model asked to turn a rough idea into a photographic prompt does much better when it can see two or three example pairs of rough-idea → final-prompt than when you describe the format in prose. Two Append Message nodes of that example pair, wired into Generate, is the cheapest quality bump in this pack. The same shape gets you draft-then-refine: generate once, keep the request and the response as history, generate again with "make it shorter".

The pack's own messages-few-shot template does exactly this - a saved history from JSON, a draft, then two Append Message nodes capturing the request and the response, then a refinement Generate that sees five earlier turns.

The two inputs and the two outputs

Required: role (system, user or assistant) and content - the exact text of the turn. Optional: messages, the earlier history you're extending.

Outputs are messages, the typed history for Generate or another Append Message, and messages_json, a versioned portable snapshot. That second output is the one people forget: it's how you save a conversation. Copy it into a llama.cpp Messages from JSON node later and you can reproduce the exact same context in a fresh workflow without rerunning anything.

Rules worth knowing before you wire it

One system turn, at the beginning. History can hold at most one system message and it has to come first. And if your history already carries a system turn, leave Generate's system_prompt empty and use a profile whose system prompt is empty too - conflicting system sources fail before the runtime is even touched. Two system prompts is not a merge, it's an error.

Profile prefixes and suffixes only wrap the current prompt, not the earlier messages. So a profile that appends "answer in JSON" doesn't retroactively change what your few-shot examples look like.

Images belong to the current turn only. Earlier messages are text-only; a previous response cannot make a previous image visible to a later request. If the second generation needs to see the picture, reconnect it - image_1 through image_10 on that Generate node, with image_amount set high enough to expose the socket.

Thinking is not appended. Wire Generate's response output to content, not thinking. The two are reported separately for a reason.

Append Message can't tell whether the text it's given was a completed generation. It receives a string. If you use return_marked_partial upstream, check the state in result_json before promoting a half-finished response into a permanent assistant turn.

And the provenance detail I like: when messages is connected, the typed result gains messages_sha256 and message_count, so you can prove later which exact history produced that output. An empty connected history gets a hash and a count of zero. The result does not embed the message bodies - export messages_json yourself if you want the snapshot.

Installing it

It ships with the pack, so there's nothing extra here beyond getting the pack in place:

comfy node install comfyui-llamacpp

Or ComfyUI Manager, searching "comfyui-llamacpp", or a source clone into ComfyUI/custom_nodes. Restart. You'll still need a llama-server build and a GGUF in ComfyUI/models/LLM/gguf/ before any of this does anything - the node is dead weight without a generation node next to it.

Where people get burned

Appending only the assistant reply. If you record "Blue." as an assistant turn without the "Name a color." user turn that produced it, your history is a monologue and the model continues it as a monologue. Capturing a conversation means capturing both sides.

Expecting the graph to remember last run. It doesn't. Rerunning rebuilds history from the literal inputs and wiring; it never appends an earlier run implicitly. If you want a growing conversation across runs, you copy the messages_json output into a Messages from JSON node by hand - and that's a deliberate act, not a cache.

Assuming history fits the context window. The node's limits are data limits (128 earlier messages, 1 MiB of serialized JSON), not a promise about your model. A long history plus a big max_tokens can still overflow. That's what llama.cpp Request Budget is for.

Reaching for cache_prompt instead. Setting cache_prompt on Generate reuses a matching prompt prefix in the KV cache. It is a speed optimisation, not conversation memory. Use these nodes for actual history.

CategoryLlamaCpp/Generate

Inputs (3)

NameTypeDefaultDescription
roleCOMBOuserTurn role.
contentSTRINGExact text of this turn.
messagesoptLLAMACPP_MESSAGESEarlier explicit text turns.

Outputs (2)

NameTypeDescription
messagesLLAMACPP_MESSAGESImmutable text history for Generate.
messages_jsonSTRINGVersioned portable JSON history.