Projection modes
Normative: MODEL.md §10.1.
The single typed event stream is canonical. A consumer may
project it into the mode-shaped views popularized by LangGraph's
stream_mode — without the engine offering a second stream:
| Mode | Projected from | Yields |
|---|---|---|
values | state | full channel state after each superstep |
updates | node_end | { [node]: update } per node that ran |
custom | custom | each ctx.emit payload |
messages | custom | just the payloads that are token chunks |
debug | every event | the event itself |
- TypeScript
- .NET (C#)
import { graph, channel, START, END, streamModes } from "@ilmek/core";
import type { ChannelMap, StateOf } from "@ilmek/core";
/** A token chunk — the payload shape the messages projection yields. */
type TokenChunk = { type: "token"; text: string };
/** Answer state — the prompt in, the streamed reply appended out. */
const AnswerState = {
prompt: channel.lastWrite<string>(""),
answer: channel.append<string>(),
} satisfies ChannelMap;
type AnswerState = StateOf<typeof AnswerState>;
const g = graph("answer", AnswerState)
.node("responder", async (state, ctx) => {
// Stream the answer token by token; tokens ride the transient channel.
for (const tok of ["Hel", "lo", "!"]) ctx.emitToken(tok);
return { answer: ["Hello!"] };
})
.edge(START, "responder")
.edge("responder", END)
.compile();
const prompt = "say hello";
// token-by-token, plus the committed update, multiplexed through one pass
for await (const part of streamModes(g, { prompt }, ["messages", "updates"])) {
if (part.mode === "messages") process.stdout.write((part.data as TokenChunk).text);
}
using Ilmek;
/// <summary>Answer state — the prompt in, the streamed reply appended out.</summary>
sealed class AnswerState
{
public string Prompt { get; set; } = "";
[Append] public List<string> Answer { get; set; } = new();
}
var g = Graph.Create<AnswerState>("answer")
.Node("responder", (state, ctx) =>
{
// Stream the answer token by token; tokens ride the transient channel.
foreach (var tok in new[] { "Hel", "lo", "!" }) ctx.EmitToken(tok);
return Update.For<AnswerState>().Append(s => s.Answer, "Hello!");
})
.Edge(Graph.Start, "responder")
.Edge("responder", Graph.End)
.Compile();
var input = Update.For<AnswerState>().Set(s => s.Prompt, "say hello");
// token-by-token, plus the committed update, multiplexed through one pass
await foreach (var part in Streaming.Projected(
IlmekRuntime.Stream(g, input),
new[] { StreamMode.Messages, StreamMode.Updates }))
{
if (part.Mode == StreamMode.Messages) Console.Write(part.Data);
}
Projection adds no information
Each projected part carries the seq and ns of the event it came from, so
reconnect and subgraph-grouping still work after projecting. project(event, modes) is a pure function of one event, so the same filter runs on a live
stream, a resumeStream, or a replay of a reconnect buffer.
Modes at a glance: values (full state per superstep) · updates
({ node: update }) · custom (every ctx.emit) · messages (token deltas) ·
debug (raw events).