The Server.LLM node was the only SourceMetaData node that didn’t behave like
the rest of the swarm. It accumulated a multi-turn conversation in meta.chat
and emitted a bespoke EventType.LLM instead of storing a result in snapshot
and emitting SNAPSHOT_UPDATE. As a result no ordinary observer (DataPoint,
SMTP, webhook) could consume its output, and ~550 lines of agentic-proposal
model classes in krill-sdk were dead weight left over from an abandoned
“LLM proposes nodes/actions/links” experiment.
The node was built during an interactive-chat phase, before the
observer-flow-collector-dispatch contract (“a node does its job, updates
itself, and stops; observers collect its flow and re-run”) existed. The chat
model and the dedicated SSE event were the wrong shape for a single-purpose
source node.
Realigned the node to the single-purpose contract across two repos. In
krill-sdk (published 0.0.40): LLMMetaData dropped chat and gained
backend (Ollama / OpenAI-compatible), systemPrompt, responseFormat
(natural language / JSON), and responseInstructions (defaulting to the new
LLMResult JSON-schema contract); the dead agentic classes were deleted.
In krill: ServerLLMProcessor now computes a prompt (system + user prompt +
input node-type contracts + live input state), dispatches to the selected
backend, stores the reply in meta.snapshot, and update()s itself so
observers wake. RESET clears the snapshot and is terminal. LLMEventPayload
and its serializer registration were removed; success surfaces via the standard
SNAPSHOT_UPDATE path. The editor lost its chat panel and gained backend /
response-format pickers and editable system-prompt + response-instructions
fields.
When adding a node type, conform it to the SourceMetaData contract from the
start: store output in snapshot, fan out by updating yourself (never a
bespoke event or a per-node history field). A node whose result can’t be read
by a generic observer is a design smell. The regression guard is
ServerLLMProcessorTest: it asserts the reply lands in the snapshot (text and
JSON modes), that each backend hits the correct path, that RESET clears and a
later EXECUTE overwrites (no history), and that failures set ERROR without
fan-out — all against a MockEngine, no real ports or paths.