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
GraphProcessable<I,O>
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?
readonlyoptionalemitsInvocationEvents?: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?
readonlyoptionalforwardsStepEvents?: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()?
optionalclose():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
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
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
Inherited from
getDefinition()
getDefinition():
GraphDefinition
Defined in: packages/core/src/graph/definition.ts:41
Returns
getFamily()?
optionalgetFamily():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
getFamilyInputPreview()?
optionalgetFamilyInputPreview():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
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
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
getPlan()
getPlan():
Plan
Defined in: packages/core/src/graph/graph.ts:179
Complete planned structure, including ordinary functions and merge outputs.
Returns
Inherited from
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.onTokento 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?
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
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?
Returns
Promise<RunOutcome<O>>
Inherited from
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()?
optionalstream(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
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?
Optional categorized callbacks and bot filter
Returns
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
});