Skip to content

Channels

See Core Concepts → Channels & state for the full explanation of why state is modeled this way. This page is the terse signature reference.

export interface Channel<V, U = V> {
readonly "~value": V;
readonly "~update": U;
readonly reduce: (current: V, update: U) => V;
readonly initial: () => V;
}
export type StateOf<C> = { readonly [K in keyof C]: C[K] extends Channel<infer V, any> ? V : never };
export function lastChannel<T>(init: T): Channel<T, T>;

Overwrite semantics — each update replaces the current value.

const review = lastChannel<ReviewResult | null>(null);
export function listChannel<T>(): Channel<readonly T[], T | readonly T[]>;

Append semantics — accepts either a single item or an array, and appends. Starts at [].

const transcript = listChannel<Msg>();
export interface InputChannel<T> extends Channel<T, T> {
readonly "~input": true;
}
export function inputChannel<T>(): InputChannel<T>;

Same overwrite semantics as lastChannel, but with no static init value — it’s seeded by whatever input a team() run is actually called with. It’s branded with "~input": true so TeamInputOf<State> can pick these channels out of a team’s state record to derive the run’s input shape:

export type TeamInputOf<State> = {
readonly [K in keyof State as State[K] extends InputChannel<any> ? K : never]:
State[K] extends InputChannel<infer T> ? T : never;
};
const issue = inputChannel<Issue>();
export const END: "~end" = "~end";
export type END = typeof END;

The sentinel a workflow() .branch/.edge target, or a team() .router, returns to terminate a run.