Skip to main content

Interface: CompiledGraph<I, O>

Defined in: packages/core/src/graph/definition.ts:40

Processable Interface

The fundamental interface that all Baleybots primitives and patterns implement. This is what enables universal composability - any Processable can be used anywhere a Baleybot is expected.

Example

// All of these implement Processable:
const bot: Processable = Baleybot.create({ ... });
const chain: Processable = Pipeline.from().chain([...]).build();
const refinement: Processable = loop({ body: bot, condition, maxIterations: 5 });

// With type inference from Zod schema:
const bot = Baleybot.create({
finalResponseSchema: z.object({ sentiment: z.string() })
});

const result = await bot.process('text'); // result is typed as { sentiment: string }

// With input/output type constraints:
const typedBot: Processable<string, { sentiment: string }> = bot;

Extends

Type Parameters

I

I

The input type of the process method (default: unknown for flexibility)

O

O

The output type of the process method (or a Zod schema to infer from)

Properties

emitsInvocationEvents?

readonly optional emitsInvocationEvents?: boolean

Defined in: packages/core/src/types.ts:332

All events from one process() call reach its own options.onToken callback. Enables isolated step events when the same instance runs concurrently. Subscription-only implementations must not overlap observed invocations.

Inherited from

GraphProcessable.emitsInvocationEvents


forwardsStepEvents?

readonly optional forwardsStepEvents?: boolean

Defined in: packages/core/src/types.ts:603

True when this processable forwards its children's stream events to ProcessOptions.hooks.onStepEvent itself.

A composite that intercepts a child's stream — today only graph() — declares this so an enclosing composite knows not to also capture and replay the same events off subscribeToAll. Without the declaration, nesting two interceptors delivers every leaf event once per level: the inner fires it live with precise attribution, the outer replays its buffered copy at step end. Declared rather than sniffed (no instanceof, no structural probe), per ASK-DONT-SNIFF.

Absent means false: the composite passes hooks through (or fires only boundaries) and its enclosing graph captures leaf streams on its behalf.

Inherited from

GraphProcessable.forwardsStepEvents

Methods

close()?

optional close(): Promise<void>

Defined in: packages/core/src/types.ts:540

Release the host resources this processable owns. Optional: a node that owns none does not implement it.

Baleybot does not implement it — it holds nothing that outlives the process. CrewSession does: a proactive-update setInterval is a host resource in the same sense a container is, because it keeps the event loop alive. A node that acquires one is expected to implement this the day it does, and every existing teardown seam then reaches it for free.

Per-node, not per-tree. A composite does not forward this to its children, and none of them implement it: the tree operation is closeAll(), which walks getChildren() once and closes whatever answers it. That is deliberate. If forwarding lived on each composite, a new composite would leak until someone remembered to add the same eight lines to it — the missing-member failure ASK-DONT-SNIFF describes, inverted. With one walker, a new resource-owning primitive is released by every existing caller the day it implements this.

Implementations are expected to be idempotent and restartable: closing twice does nothing the second time, and a process() after a close() rebuilds what it needs. That is what lets teardown seams call this without knowing whether anyone else still holds the node.

Returns

Promise<void>

Inherited from

GraphProcessable.close


getBotNames()

getBotNames(): string[]

Defined in: packages/core/src/types.ts:473

Get all bot names in this processable (including nested processables)

Returns

string[]

Array of bot names that can emit events

Example

const pipeline = pipeline()
.step(chatBot)
.parallel({ sentiment: sentimentBot, tone: toneBot })
.build();

const botNames = pipeline.getBotNames();
// ['assistant', 'sentiment', 'tone']

Inherited from

GraphProcessable.getBotNames


getChildren()

getChildren(): ProcessableChild[]

Defined in: packages/core/src/types.ts:514

The processables this one composes, each tagged with how it is reached.

Composition already describes the shape of the run: pipeline(a, b, c) is a series, loop({ body }) is a cycle, conditional({ onPass, onFail }) is a branch. So there is no separate edge list to maintain and no description type to keep in sync — a consumer walks this tree and reads the topology off the edge tags.

Returns the live processables, not a projection of them, so a caller can recurse with the same two questions at every level. Anything wanting a serialisable form builds it on its own side; nothing here decides what is safe to send anywhere.

Leaves return []. So do composites whose children are not processables — recursiveLoop's body is a plain closure, catchStep's handler is a function — which is why a returned [] means "nothing to walk", not "nothing happens here".

Returns

ProcessableChild[]

Inherited from

GraphProcessable.getChildren


getDefinition()

getDefinition(): GraphDefinition

Defined in: packages/core/src/graph/definition.ts:41

Returns

GraphDefinition


getFamily()?

optional getFamily(): DeclaredFamily | undefined

Defined in: packages/core/src/types.ts:561

The family this processable belongs to, when it was built as a member of one. Optional: a node built directly does not implement it.

The counterpart, for any Processable, of the family a Baleybot carries on its config. baleybotFamily() members answer from their config; processableFamily() attaches this to each member it builds, which is what lets the dev panel show N pipelines as one family with a roster rather than N unrelated compositions.

An accessor rather than a field for the reason getGraphNodeLabels() is one: the registry holds live nodes and projects them on demand, so a declaration read at list time cannot go stale against the object it describes. Read it through declaredFamilyOf() — that is the one place the result is validated, and a half-filled declaration counts as none.

Absent means "not a family member", which is the common case and stays free: nothing on the hot path calls this.

Returns

DeclaredFamily | undefined

Inherited from

GraphProcessable.getFamily


getFamilyInputPreview()?

optional getFamilyInputPreview(): string | undefined

Defined in: packages/core/src/types.ts:586

A truncated JSON rendering of the input this family member was built from, for the dev panel. Optional, and only ever attached in dev mode.

A string, deliberately, and named for it: the family memo runs in production, where the dev registry does not, and build inputs are routinely whole domain objects — a ticket, a tenant record, a request context. Holding one would keep it alive for the life of the process and let it mutate under whatever is displaying it. A rendering copies what it needs, bounded, and can carry neither a reference nor a live tool.

Answers the question the member key usually cannot. With the default derivation the key is the serialised input, so the two agree; the moment a caller passes key: (i) => i.team — which is the recommended shape, since keying on a whole object over-discriminates and costs a serialisation per call — the key becomes billing and everything else about the input is gone. This is what puts it back.

Not a security boundary and not sanitised beyond truncation: whatever the developer passed to their own build function is rendered as JSON. That is the same posture as the goal and prompt the panel already shows, and the same dev-mode-and-loopback gate covers it.

Returns

string | undefined

Inherited from

GraphProcessable.getFamilyInputPreview


getGraphEdges()

getGraphEdges(): object[]

Defined in: packages/core/src/graph/graph.ts:185

Edges as pairs of node ids, matching getChildren() labels.

Returns

object[]

Inherited from

GraphProcessable.getGraphEdges


getGraphNodeLabels()

getGraphNodeLabels(): Record<string, string>

Defined in: packages/core/src/graph/graph.ts:189

Compatibility projection of explicit labels. Prefer getGraphNodes().

Returns

Record<string, string>

Inherited from

GraphProcessable.getGraphNodeLabels


getGraphNodes()

getGraphNodes(): GraphNodeRecord[]

Defined in: packages/core/src/graph/graph.ts:181

Complete node inspection, including ordinary function steps.

Returns

GraphNodeRecord[]

Inherited from

GraphProcessable.getGraphNodes


getGraphNodeSources()

getGraphNodeSources(): Record<string, string>

Defined in: packages/core/src/graph/graph.ts:187

Compatibility projection of function source. Prefer getGraphNodes().

Returns

Record<string, string>

Inherited from

GraphProcessable.getGraphNodeSources


getId()

getId(): string

Defined in: packages/core/src/types.ts:398

Get the unique ID of this processable instance

Each processable has a unique ID that persists for its lifetime. IDs are useful for:

  • Filtering specific instances in subscriptions
  • Tracking performance metrics per instance
  • Debugging nested compositions

Returns

string

Unique ID string

Example

const bot1 = Baleybot.create({ name: 'analyzer' });
const bot2 = Baleybot.create({ name: 'analyzer' });

bot1.getId(); // 'baleybot-1-a3f891'
bot2.getId(); // 'baleybot-2-b4c2d3'

// Same semantic name, different IDs
bot1.getName(); // 'analyzer'
bot2.getName(); // 'analyzer'

Inherited from

GraphProcessable.getId


getName()

getName(): string

Defined in: packages/core/src/types.ts:493

What this processable is called. A label, not a key.

Stable across runs, which getId() is not — an id embeds a timestamp and changes on every restart. That is what makes a name safe to print in a log, show on a node, or hand to a merge function.

Not unique. Two bots may both be called 'analyzer', and every unnamed loop() reports 'loop'. Anything that must address exactly one instance uses getId(), or an address — never this. An earlier version of this contract promised uniqueness and had unnamed primitives return their instance id to deliver it; that put generated ids into ParallelMergeInput.botNames and graph node labels, where a name was wanted, and made both change on every restart.

Never returns an empty string: where no name was configured, the primitive reports its kind.

Returns

string

Inherited from

GraphProcessable.getName


getPlan()

getPlan(): Plan

Defined in: packages/core/src/graph/graph.ts:179

Complete planned structure, including ordinary functions and merge outputs.

Returns

Plan

Inherited from

GraphProcessable.getPlan


process()

process(input, options?): Promise<InferSchemaOutput<O>>

Defined in: packages/core/src/types.ts:364

Process input and return structured output

You can use BOTH streaming events AND final result together:

  • Streaming events: Provide options.onToken to receive real-time events during execution
  • Final result: Returned when Promise resolves (complete parsed response)

Both work simultaneously - streaming events fire in real-time, then final result is returned.

Parameters

input

I

Input data (typed for safety)

options?

ProcessOptions

Optional callbacks for streaming, completion, errors

Returns

Promise<InferSchemaOutput<O>>

Promise resolving to structured output (inferred from TOutput if it's a Zod schema)

Example

Using both streaming and final result

// Stream events in real-time AND get final result
const result = await bot.process(input, {
onToken: {
onTextDelta(botName, event) {
process.stdout.write(event.content); // Real-time streaming
},
onToolExecutionStart(botName, event) {
console.log(`Tool starting: ${event.toolName}`); // Real-time tool status
}
}
});

// Also get final result when Promise resolves
console.log('Complete response:', result); // Final parsed response

Inherited from

GraphProcessable.process


run()

run(input, options?): Promise<RunOutcome<O>>

Defined in: packages/core/src/graph/graph.ts:183

Managed outcome; successful undefined and null remain completed values.

Parameters

input

I

options?

ProcessOptions

Returns

Promise<RunOutcome<O>>

Inherited from

GraphProcessable.run


runDurable()

runDurable(input, options): Promise<RunOutcome<O>>

Defined in: packages/core/src/graph/definition.ts:42

Parameters

input

I

options

ProcessOptions & object

Returns

Promise<RunOutcome<O>>


stream()?

optional stream(input, options?): AsyncGenerator<BaleybotStreamEvent>

Defined in: packages/core/src/types.ts:372

Stream events as an async iterator

Alternative to callback-based streaming via onToken/onComplete/onError. Uses for await pattern for cleaner consumption.

Parameters

input

I

options?

Omit<ProcessOptions, "onToken" | "onError" | "onComplete">

Returns

AsyncGenerator<BaleybotStreamEvent>

Inherited from

GraphProcessable.stream


subscribeToAll()

subscribeToAll(options?): Subscription

Defined in: packages/core/src/types.ts:455

Subscribe to categorized events from this processable and all nested processables

Provides fine-grained control over event handling with categorized callbacks:

  • onStreamEvent: Real-time content streaming (text_delta, structured_output_delta, tool_call_arguments_delta)
  • onProgressUpdate: Status updates (tool_execution_start, tool_call_stream_start, tool_call_stream_complete, reasoning)
  • onComplete: Emitted when each bot finishes processing with final output
  • onError: Error events (error, tool_validation_error)

Callbacks now receive both bot ID and name for precise identification:

  • botId: Unique instance identifier (e.g., 'baleybot-1-a3f891')
  • botName: Semantic name (e.g., 'analyzer', 'router', 'processor')

Parameters

options?

SubscribeAllCallbacks

Optional categorized callbacks and bot filter

Returns

Subscription

Subscription object with unsubscribe method

Examples

Subscribe to all events

const subscription = pipeline.subscribeToAll({
onStreamEvent(botId, botName, event) {
// Real-time streaming content
if (event.type === 'text_delta') {
console.log(`[${botId}] ${botName}:`, event.content);
}
},
onProgressUpdate(botId, botName, event) {
// Status updates
if (event.type === 'tool_execution_start') {
console.log(`${botName} (${botId}) executing ${event.toolName}`);
}
},
onComplete(botId, botName, output) {
// Bot finished processing
console.log(`${botName} completed:`, output);
},
onError(botId, botName, event) {
// Errors
console.error(`${botName} error:`, event);
}
});

await pipeline.process('input');
subscription.unsubscribe();

Filter specific bots by name or ID

const subscription = pipeline.subscribeToAll({
onStreamEvent(botId, botName, event) {
console.log(`${botName}:`, event);
},
omit: ['chat-step', 'processor-42-8a3f91'] // Filter by name or ID
});

Inherited from

GraphProcessable.subscribeToAll