kaboo_workflows¶
The top-level package. See API reference overview for the curated map of the public surface grouped by import path.
kaboo_workflows ¶
kaboo-workflows — YAML-driven multi-agent orchestration with AG-UI/CopilotKit support.
AppConfig ¶
Bases: BaseModel
Root YAML configuration.
Orchestrations are defined as a dict of named orchestration blocks that can reference each other for arbitrary nesting.
version
class-attribute
instance-attribute
¶
version = '1'
Schema version — omit to use the default "1".
history
class-attribute
instance-attribute
¶
history = False
Global default for per-agent history:.
Applies to any agent that does not set its own history:. Defaults to
False so sub-agents are stateless per run unless opted in. The entry
agent is unaffected — its transcript is always the CopilotKit chat.
ResolvedConfig
dataclass
¶
ResolvedConfig(
*,
agents=dict(),
orchestrators=dict(),
entry,
mcp_lifecycle=MCPLifecycle(),
app_config=None,
)
Fully resolved config — lifecycle started, agents ready.
After calling :func:~kaboo_workflows.config.loaders.load, use
:meth:wire_event_queue to set up event streaming::
resolved = load("config.yaml")
event_queue = resolved.wire_event_queue()
wire_event_queue ¶
wire_event_queue(*, session_id=None, tool_labels=None)
Wire all agents and orchestrators for event streaming.
This is the recommended way to set up event streaming. It:
- Builds a :class:
~kaboo_workflows.types.SessionManifestfrom the resolved runtime objects. - Auto-resolves stream groups from the original AppConfig so every agent is attributed to a hierarchical dot-path group.
- Collects tool labels from MCP clients and agents in the original AppConfig (agent-level overrides MCP-level).
- Wires every agent (and orchestrator) with an
:class:
~kaboo_workflows.hooks.EventPublishervia :func:~kaboo_workflows.wire.make_event_queue. - Emits a SESSION_START event carrying the manifest as the first event on the queue.
.. warning::
This **mutates** the agents and orchestrators stored on this
instance by adding hooks and overwriting ``callback_handler``.
Call it only once per ``ResolvedConfig`` instance.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
session_id
|
str | None
|
Optional session ID to embed in events. |
None
|
tool_labels
|
dict[str, str] | None
|
Optional runtime tool name → display label mapping. These take highest priority, overriding both agent-level and MCP-client-level labels from YAML config. |
None
|
Returns:
| Type | Description |
|---|---|
EventQueue
|
A ready-to-use :class: |
EventQueue
|
SESSION_START already on it. |
Raises:
| Type | Description |
|---|---|
ValueError
|
If the entry node cannot be resolved by object identity. |
Source code in src/kaboo_workflows/config/resolvers/config.py
45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 | |
ResolvedInfra
dataclass
¶
ResolvedInfra(
models=dict(),
clients=dict(),
mcp_lifecycle=MCPLifecycle(),
)
Infrastructure resolved from config — lifecycle NOT started.
This is the pure result of :func:resolve_infra. Lifecycle is cold,
agents are not yet created.
Session managers are NOT stored here — they are built per agent and per
orchestration at session time, from config.session_manager (the global
def) plus effective_session_id computed by load_session.
Use :func:~kaboo_workflows.config.loaders.load for a fully
activated system, or manually::
infra = resolve_infra(config)
infra.mcp_lifecycle.start()
agents = resolve_agents(agent_defs=config.agents, ...)
OrchestrationBuilder ¶
OrchestrationBuilder(
configs,
agents,
agent_defs,
models,
mcp_clients,
*,
global_session_manager_def=None,
session_id=None,
)
Builds all named orchestrations in dependency order.
Initialize the OrchestrationBuilder.
Orchestrations are sorted topologically so that each orchestration's dependencies (other orchestrations it references) are built first. The node pool grows as orchestrations are built, making earlier results available to downstream configurations.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
configs
|
Mapping[str, OrchestrationDef]
|
Orchestration definitions keyed by name. |
required |
agents
|
Mapping[str, Agent]
|
Already-resolved agents keyed by name. |
required |
agent_defs
|
dict[str, AgentDef]
|
Agent schema definitions for delegate forking. |
required |
models
|
dict[str, Model]
|
Resolved model objects keyed by name. |
required |
mcp_clients
|
dict[str, MCPClient]
|
Resolved MCP client objects keyed by name. |
required |
global_session_manager_def
|
SessionManagerDef | None
|
Global session manager def from
|
None
|
session_id
|
str | None
|
Effective session id threaded down from |
None
|
Source code in src/kaboo_workflows/config/resolvers/orchestrations/builders.py
46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 | |
build_all ¶
build_all()
Build all orchestrations in topological order.
Returns:
| Type | Description |
|---|---|
dict[str, Node]
|
Dict of orchestration name -> built orchestration (Swarm | Graph | Agent). |
Source code in src/kaboo_workflows/config/resolvers/orchestrations/builders.py
87 88 89 90 91 92 93 94 95 | |
CircularDependencyError ¶
Bases: ConfigurationError
Raised when orchestration definitions contain a dependency cycle.
ConfigurationError ¶
Bases: ValueError
Raised when a kaboo-workflows configuration is invalid.
Subclasses ValueError so callers that catch ValueError still work,
but allows more specific except ConfigurationError handling.
ImportResolutionError ¶
Bases: ConfigurationError
Raised when a custom import spec (module.path:Name) cannot be loaded.
SchemaValidationError ¶
Bases: ConfigurationError
Raised when YAML config fails Pydantic schema validation.
UnresolvedReferenceError ¶
Bases: ConfigurationError
Raised when a config references a non-existent model, agent, or MCP resource.
EventPublisher ¶
EventPublisher(
callback,
agent_name,
*,
tool_labels=None,
stream_group="",
stream_title="",
is_chat_reply=False,
max_result_len=600,
)
Bases: HookProvider
Unified event publisher for single-agent and multi-agent orchestrations.
Initialize the EventPublisher.
Converts strands hook events into :class:StreamEvent objects and
delivers them to an external callback. Emits an AGENT_COMPLETE event at
the end of each invocation with usage metrics from EventLoopMetrics.
For TOKEN and REASONING events use :meth:as_callback_handler to
create a strands-compatible callback_handler.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
callback
|
EventCallback
|
Called with each :class: |
required |
agent_name
|
str
|
Identifier for the agent or orchestrator. |
required |
tool_labels
|
dict[str, str] | None
|
Optional mapping of tool names to display labels. |
None
|
stream_group
|
str
|
Dot-path stream group for hierarchical activity attribution. Empty string when not configured. |
''
|
stream_title
|
str
|
Human-readable title for the stream group. |
''
|
max_result_len
|
int
|
Maximum character length for tool result text in TOOL_END events. Default: 600. |
600
|
Example::
publisher = EventPublisher(callback=on_event, agent_name="analyzer")
agent = Agent(
hooks=[publisher],
callback_handler=publisher.as_callback_handler(),
)
Source code in src/kaboo_workflows/hooks/event_publisher.py
179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 | |
mark_inline_chat_owner ¶
mark_inline_chat_owner()
Flag this publisher's group as the inline chat owner (see __init__).
Source code in src/kaboo_workflows/hooks/event_publisher.py
239 240 241 | |
register_hooks ¶
register_hooks(registry, **kwargs)
Register hook callbacks for agent and multiagent events.
Source code in src/kaboo_workflows/hooks/event_publisher.py
256 257 258 259 260 261 262 263 264 265 266 267 268 269 | |
as_callback_handler ¶
as_callback_handler()
Return a strands-compatible callback_handler for TOKEN, REASONING, and HANDOFF events.
Handles the following kwarg patterns emitted by strands:
- data (str): A streamed text chunk -> TOKEN event.
- reasoningText (str): A reasoning chunk -> REASONING event.
- type == "multiagent_handoff": A :class:~strands.types._events.MultiAgentHandoffEvent
fired during Swarm/Graph node transitions -> HANDOFF event.
Returns:
| Type | Description |
|---|---|
Callable[..., None]
|
A callable compatible with strands |
Source code in src/kaboo_workflows/hooks/event_publisher.py
551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 566 567 568 569 570 571 572 573 574 575 576 577 578 579 580 581 582 583 584 585 586 587 588 589 590 591 592 593 594 595 596 597 598 599 600 | |
MaxToolCallsGuard ¶
MaxToolCallsGuard(max_calls=25)
Bases: HookProvider
Stops the agent after a maximum number of tool calls per invocation.
Initialize the MaxToolCallsGuard.
On first violation the LLM is instructed to stop using tools and write a final answer (graceful shutdown). If the LLM ignores that and requests another tool call, the event loop is terminated immediately (hard stop).
Uses strands' invocation_state dict for per-invocation state — the
counter and flags reset automatically on each new agent() call.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
max_calls
|
int
|
Maximum tool calls allowed per invocation. Default: 25. |
25
|
Source code in src/kaboo_workflows/hooks/max_calls_guard.py
40 41 42 43 44 45 46 47 48 49 50 51 52 53 | |
register_hooks ¶
register_hooks(registry, **kwargs)
Register the BeforeToolCallEvent callback.
Source code in src/kaboo_workflows/hooks/max_calls_guard.py
55 56 57 58 | |
StopGuard ¶
StopGuard(stop_check)
Bases: HookProvider
Cancels the agent's event loop when an external stop condition is met.
Initialize the StopGuard.
The stop condition is checked before every tool call. When it returns
True, the current tool is cancelled and the event loop is stopped.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
stop_check
|
Callable[[], bool]
|
Callable that returns
|
required |
Source code in src/kaboo_workflows/hooks/stop_guard.py
29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 | |
register_hooks ¶
register_hooks(registry, **kwargs)
Register the before-tool-call guard.
Source code in src/kaboo_workflows/hooks/stop_guard.py
45 46 47 48 | |
ToolNameSanitizer ¶
Bases: HookProvider
Strips model-injected artifacts from tool names so Strands can look them up.
Registers on: - AfterModelCallEvent: rewrites tool names in the model response message. - BeforeToolCallEvent: safety net — fixes or cancels still-garbled names.
register_hooks ¶
register_hooks(registry, **kwargs)
Register AfterModelCall and BeforeToolCall sanitization callbacks.
Source code in src/kaboo_workflows/hooks/tool_name_sanitizer.py
93 94 95 96 97 | |
MCPLifecycle ¶
MCPLifecycle(server_ready_timeout=30)
Manages MCP server and client lifecycle ordering.
Initialize the MCPLifecycle.
Ensures servers are fully ready before clients connect, and clients are stopped before servers on shutdown.
Example::
lifecycle = MCPLifecycle()
lifecycle.add_server("postgres", pg_server)
lifecycle.add_client("pg_client", pg_client)
with lifecycle:
# All servers started and ready, all clients connected
agent = Agent(tools=[lifecycle.get_client("pg_client")])
agent("Query the database")
# All cleaned up
Or without context manager::
lifecycle.start()
try:
...
finally:
lifecycle.stop()
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
server_ready_timeout
|
float
|
Seconds to wait for each server to become ready. |
30
|
Source code in src/kaboo_workflows/mcp/lifecycle.py
31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 | |
add_server ¶
add_server(name, server)
Register an MCP server.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
name
|
str
|
Unique server identifier. |
required |
server
|
MCPServer
|
The MCP server instance. |
required |
Raises:
| Type | Description |
|---|---|
ValueError
|
If a server with this name is already registered. |
Source code in src/kaboo_workflows/mcp/lifecycle.py
67 68 69 70 71 72 73 74 75 76 77 78 79 | |
add_client ¶
add_client(name, client)
Register an MCP client.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
name
|
str
|
Unique client identifier. |
required |
client
|
MCPClient
|
The strands MCP client instance. |
required |
Raises:
| Type | Description |
|---|---|
ValueError
|
If a client with this name is already registered. |
Source code in src/kaboo_workflows/mcp/lifecycle.py
81 82 83 84 85 86 87 88 89 90 91 92 93 | |
get_server ¶
get_server(name)
Get a registered server by name.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
name
|
str
|
Server identifier. |
required |
Returns:
| Type | Description |
|---|---|
MCPServer
|
The registered MCP server. |
Raises:
| Type | Description |
|---|---|
KeyError
|
If no server with this name is registered. |
Source code in src/kaboo_workflows/mcp/lifecycle.py
95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 | |
get_client ¶
get_client(name)
Get a registered client by name.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
name
|
str
|
Client identifier. |
required |
Returns:
| Type | Description |
|---|---|
MCPClient
|
The registered strands MCP client. |
Raises:
| Type | Description |
|---|---|
KeyError
|
If no client with this name is registered. |
Source code in src/kaboo_workflows/mcp/lifecycle.py
111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 | |
start ¶
start()
Start all servers and wait for readiness.
Idempotent: if already started, returns immediately.
load() calls this before creating agents (so MCP clients can
connect), and the context manager calls it again on enter — the
second call is a no-op. The context manager is still needed for
graceful shutdown via stop().
Clients are not started here — strands automatically starts MCPClient instances when they are registered as tool providers on an Agent. Starting them here would cause a "session is currently running" error when the Agent tries to start them again. Instead we pin each client with a permanent lifecycle consumer so its session is not torn down when consuming Agents are garbage-collected.
Raises:
| Type | Description |
|---|---|
RuntimeError
|
If any server fails to start or become ready. |
Source code in src/kaboo_workflows/mcp/lifecycle.py
127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 | |
stop ¶
stop()
Stop all clients first, then all servers.
Clients that were never started (e.g., never registered on an Agent) are skipped gracefully.
Source code in src/kaboo_workflows/mcp/lifecycle.py
174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 | |
__enter__ ¶
__enter__()
Start lifecycle on context entry.
Source code in src/kaboo_workflows/mcp/lifecycle.py
202 203 204 205 | |
__exit__ ¶
__exit__(exc_type, exc_val, exc_tb)
Stop lifecycle on context exit.
Source code in src/kaboo_workflows/mcp/lifecycle.py
207 208 209 210 211 212 213 214 | |
__aenter__
async
¶
__aenter__()
Async context entry — delegates to sync :meth:start.
Useful with Starlette / ASGI lifespan::
@asynccontextmanager
async def lifespan(app):
async with lifecycle:
yield
Source code in src/kaboo_workflows/mcp/lifecycle.py
216 217 218 219 220 221 222 223 224 225 226 227 | |
__aexit__
async
¶
__aexit__(exc_type, exc_val, exc_tb)
Async context exit — delegates to sync :meth:stop.
Source code in src/kaboo_workflows/mcp/lifecycle.py
229 230 231 232 233 234 235 236 | |
AnsiRenderer ¶
AnsiRenderer(
*, file=None, separator_width=None, typewriter_delay=0.0
)
Bases: EventRenderer
Renders events using raw ANSI escape codes.
Initialize the AnsiRenderer.
No third-party dependencies. Colour codes are automatically suppressed when stdout is not a TTY (piped / redirected output).
Token and reasoning events are written inline (no trailing newline) so they appear as a continuous stream. A separator line is printed when the mode changes between reasoning and responding, or when the active agent changes.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
file
|
Any | None
|
Output stream (defaults to |
None
|
separator_width
|
int | None
|
Width of separator lines. Defaults to 70 or the current terminal width, whichever is available. |
None
|
typewriter_delay
|
float
|
Seconds to sleep after each printable character
in TOKEN and REASONING events. |
0.0
|
Source code in src/kaboo_workflows/renderers/ansi.py
36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 | |
EventType ¶
Bases: StrEnum
Event type constants for :class:StreamEvent.
StrEnum values are plain strings, so == "token" comparisons
work unchanged.
StreamEvent
dataclass
¶
StreamEvent(
type,
agent_name,
timestamp=(lambda: datetime.now(tz=(timezone.utc)))(),
data=dict(),
)
A typed event from agent or multi-agent execution.
Per-agent activity (AGENT_START, TOKEN, REASONING,
TOOL_START, TOOL_END, INTERRUPT, AGENT_COMPLETE, ERROR,
NODE_START, NODE_STOP, HANDOFF, MULTIAGENT_START,
MULTIAGENT_COMPLETE) is produced by
:class:~kaboo_workflows.hooks.EventPublisher. Session-level events
(SESSION_START, SESSION_END) are produced by the queue/wiring
layer in :mod:kaboo_workflows.wire.
Attributes:
| Name | Type | Description |
|---|---|---|
type |
str
|
Event type identifier (one of the :class: |
agent_name |
str
|
Name of the agent or session entry point that produced this event. |
timestamp |
datetime
|
When the event occurred. |
data |
dict[str, Any]
|
Event-specific payload. |
asdict ¶
asdict()
Convert this StreamEvent to a flat dict for serialization.
Source code in src/kaboo_workflows/types.py
88 89 90 91 92 | |
from_dict
classmethod
¶
from_dict(data)
Deserialize a dict into a StreamEvent.
Restores all fields produced by :meth:asdict, including the
timestamp (parsed from its ISO-8601 string representation).
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
data
|
dict[str, Any]
|
A dict as produced by :meth: |
required |
Returns:
| Type | Description |
|---|---|
StreamEvent
|
A new StreamEvent instance. |
Source code in src/kaboo_workflows/types.py
94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 | |
__eq__ ¶
__eq__(other)
Compare events by type, agent_name, and data (ignoring timestamp).
Source code in src/kaboo_workflows/types.py
122 123 124 125 126 | |
__hash__ ¶
__hash__()
Hash based on type and agent_name (data is unhashable).
Source code in src/kaboo_workflows/types.py
128 129 130 | |
EventQueue ¶
EventQueue(queue, *, entry_name=None, session_id=None)
Async event queue with hidden end-of-stream sentinel and session lifecycle.
Initialize the EventQueue.
Callers consume events via :meth:get (which returns None when the
stream is closed) and signal completion via :meth:close. The sentinel
is an implementation detail — user code never sees or owns it.
entry_name and session_id parameterise the SESSION_START and
SESSION_END events emitted by :meth:emit_session_start and
:meth:close respectively.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
queue
|
Queue
|
The underlying asyncio.Queue to wrap. |
required |
entry_name
|
str | None
|
The configured name of the entry node. Used as
|
None
|
session_id
|
str | None
|
The effective session id. Included in the
SESSION_END event payload as |
None
|
Source code in src/kaboo_workflows/wire.py
49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 | |
flush ¶
flush()
Discard all currently queued events and reset lifecycle guards.
Call this at the start of each request to clear any stale events left over from a previous invocation. Resets the SESSION_START and SESSION_END guards so the next invocation cycle can re-emit them.
Source code in src/kaboo_workflows/wire.py
98 99 100 101 102 103 104 105 106 107 108 | |
emit_session_start ¶
emit_session_start(manifest)
Emit a SESSION_START event with the session manifest.
Places a :class:StreamEvent with type=EventType.SESSION_START on
the queue. A guard prevents double-emission within the same
invocation cycle (reset by :meth:flush).
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
manifest
|
SessionManifest
|
The :class: |
required |
Source code in src/kaboo_workflows/wire.py
110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 | |
get
async
¶
get()
Wait for the next event.
Returns:
| Type | Description |
|---|---|
StreamEvent | None
|
The next :class: |
StreamEvent | None
|
has been closed via :meth: |
Source code in src/kaboo_workflows/wire.py
137 138 139 140 141 142 143 144 145 | |
put_event ¶
put_event(event)
Place an event on the queue (non-blocking, thread-safe).
Useful for injecting out-of-band events such as error signals.
Source code in src/kaboo_workflows/wire.py
147 148 149 150 151 152 | |
close
async
¶
close(data=None)
Signal end-of-stream.
Emits a SESSION_END event before placing the sentinel on the queue.
A guard prevents double-emission within the same invocation cycle
(reset by :meth:flush). Subsequent close() calls are no-ops
for the SESSION_END emission but still place the sentinel — the
method remains idempotent.
Typically called in a finally block after the agent invocation
finishes.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
data
|
dict[str, Any] | None
|
Additional data to include in the SESSION_END event. |
None
|
Source code in src/kaboo_workflows/wire.py
154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 | |
load ¶
load(config)
Load config from file(s) or YAML string(s) and resolve to live objects.
This is the main entry point for zero-code usage.
Accepts a single source or a list of sources. Each source is either
a file path (str or Path) or a raw YAML string. File paths
are detected by checking if the path exists on disk; anything else
is parsed as inline YAML.
Pipeline:¶
- Parse each source (file read or inline YAML)
- Per-source: strip anchors, interpolate variables
- Sanitize collection keys (spaces/special chars -> underscores)
- Merge sources (if multiple), detect duplicate names
- Validate against schema (Pydantic)
- Resolve infrastructure (models, MCP — pure; no session manager)
- Start MCP servers (so clients can connect)
- Create agents (Agent.init auto-starts MCP clients)
- Wire orchestration / entry point
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
config
|
ConfigInput | list[ConfigInput]
|
File path, raw YAML string, or list of either. |
required |
Returns:
| Type | Description |
|---|---|
ResolvedConfig
|
ResolvedConfig with agents, entry (callable), and mcp_lifecycle. |
Raises:
| Type | Description |
|---|---|
FileNotFoundError
|
Config file doesn't exist. |
ConfigurationError
|
Invalid YAML syntax, schema validation failure, or invalid references. |
REMARKS:¶
When multiple sources are provided, collection sections (agents,
models, mcp_servers, mcp_clients, orchestrations) are
merged. Duplicate names within the same section raise ValueError.
Singleton fields (entry, session_manager, log_level) use
last-wins semantics.
Side effect: this function starts MCP servers during resolution.
Agent.__init__ auto-starts MCP clients (via process_tools() ->
MCPClient.load_tools()), and those clients need running servers to
connect to. MCPLifecycle.start() is called before agent
creation to satisfy this dependency.
MCPLifecycle.start() is idempotent, so the caller's context
manager (async with resolved.mcp_lifecycle:) is a no-op on enter
but still required for graceful shutdown — __aexit__ stops
clients first, then servers.
Source code in src/kaboo_workflows/config/loaders/loaders.py
82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 | |
load_config ¶
load_config(config)
Parse and validate config from file(s) or YAML string(s).
Accepts a single source or a list. Each source is auto-detected:
Pathobjects are always treated as file paths.strvalues are treated as file paths if the file exists on disk; otherwise they are parsed as inline YAML content.
When multiple sources are provided, their collection sections
(agents, models, mcp_servers, mcp_clients,
orchestrations) are merged. Duplicate names within the same
section raise ValueError. Singleton fields (entry,
session_manager, log_level) use last-wins semantics.
Each source's vars: block is applied only to that source
(interpolation is per-source).
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
config
|
ConfigInput | list[ConfigInput]
|
File path, raw YAML string, or list of either. |
required |
Returns:
| Type | Description |
|---|---|
AppConfig
|
Validated AppConfig instance. |
Raises:
| Type | Description |
|---|---|
FileNotFoundError
|
A |
ConfigurationError
|
Invalid YAML, schema validation failure, or invalid references. |
Source code in src/kaboo_workflows/config/loaders/loaders.py
146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 | |
load_session ¶
load_session(config, infra, *, session_id=None)
Create agents and orchestration from already-started infrastructure.
This is the session-level counterpart to :func:load. Use it when
you want to share MCP servers across multiple sessions (e.g. one
session per HTTP request) while creating isolated agents per
session.
infra does NOT carry a session manager; instances are built per
agent/orchestration from config.session_manager plus an
effective_session_id computed here.
Typical server pattern::
app_config = load_config("config.yaml")
infra = resolve_infra(app_config)
infra.mcp_lifecycle.start()
# Per request:
resolved = load_session(app_config, infra, session_id="abc")
infra.mcp_lifecycle must already be started before calling this.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
config
|
AppConfig
|
The validated AppConfig. |
required |
infra
|
ResolvedInfra
|
Resolved infrastructure with servers already started. |
required |
session_id
|
str | None
|
Optional runtime session ID. Combined with
|
None
|
Returns:
| Type | Description |
|---|---|
ResolvedConfig
|
ResolvedConfig with freshly created agents and entry point. |
Note
No SessionManager instance is built in this function; instances
are constructed at the leaves (build_agent_from_def,
OrchestrationBuilder._build_one).
Source code in src/kaboo_workflows/config/loaders/loaders.py
197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 | |
resolve_infra ¶
resolve_infra(config)
Resolve infrastructure from an AppConfig (pure, no I/O).
Creates model objects, MCP server/client objects, and a lifecycle manager. Nothing is started.
Resolution order:
- Models (no dependencies)
- MCP servers (no dependencies)
- MCP clients (depend on servers)
- MCP lifecycle (assembles servers + clients, not started)
- Session manager validation only —
agentcoreprovider rejected globally; no instance is constructed (instances are built per-leaf at session time).
Agents and orchestration are resolved in :func:load after
mcp_lifecycle.start() because Agent.__init__ auto-starts
MCP clients which need servers to be running first. The lifecycle
start in load() is idempotent — the context manager is still
used for graceful shutdown.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
config
|
AppConfig
|
Parsed AppConfig from YAML. |
required |
Returns:
| Name | Type | Description |
|---|---|---|
A |
ResolvedInfra
|
class: |
Source code in src/kaboo_workflows/config/resolvers/config.py
396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 | |
create_mcp_client ¶
create_mcp_client(
*,
server=None,
url=None,
command=None,
transport=DEFAULT_TRANSPORT,
transport_options=None,
**kwargs,
)
Create a strands MCPClient from connection configuration.
Exactly one of server, url, or command must be provided.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
server
|
MCPServer | None
|
A managed MCPServer instance (connects via its URL). |
None
|
url
|
str | None
|
External MCP server URL (for SSE or streamable-http). |
None
|
command
|
list[str] | None
|
Command to start an MCP server subprocess (stdio transport). |
None
|
transport
|
MCP_TRANSPORT
|
Override transport type ("stdio", "sse", "streamable-http"). Auto-detected if not specified. |
DEFAULT_TRANSPORT
|
transport_options
|
dict[str, Any] | None
|
Extra kwargs forwarded to the transport factory. These are transport-specific — see each transport function for available options: stdio: sse: streamable-http: |
None
|
**kwargs
|
Any
|
Additional kwargs forwarded to strands MCPClient (startup_timeout, tool_filters, prefix, elicitation_callback, tasks_config, etc.). |
{}
|
Returns:
| Type | Description |
|---|---|
MCPClient
|
A strands MCPClient instance. |
Raises:
| Type | Description |
|---|---|
ValueError
|
If connection parameters are ambiguous. |
Source code in src/kaboo_workflows/mcp/client.py
26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 | |
create_mcp_server ¶
create_mcp_server(
*,
name,
tools,
host="127.0.0.1",
port=8000,
transport=DEFAULT_TRANSPORT,
server_params=None,
)
Create an MCP server from a list of callables — no subclassing needed.
Each callable (sync or async) is registered as a tool on the underlying
FastMCP instance. For advanced use (custom state, routes, resources),
subclass :class:MCPServer directly.
Example::
def get_weather(city: str) -> str:
return f"Sunny in {city}"
async def query_db(sql: str) -> str: ...
server = create_mcp_server(name="weather", tools=[get_weather, query_db], port=8001)
server.start()
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
name
|
str
|
Unique server identifier. |
required |
tools
|
list[Callable[..., Any]]
|
Callables to register as MCP tools. |
required |
host
|
str
|
Bind address (default |
'127.0.0.1'
|
port
|
int
|
Bind port (default |
8000
|
transport
|
MCP_SERVER_TRANSPORT
|
Server transport type ( |
DEFAULT_TRANSPORT
|
server_params
|
dict[str, Any] | None
|
Extra kwargs forwarded to |
None
|
Returns:
| Type | Description |
|---|---|
MCPServer
|
A ready-to-use :class: |
Source code in src/kaboo_workflows/mcp/server.py
276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 | |
node_as_async_tool ¶
node_as_async_tool(node, *, name=None, description)
Wrap an Agent or MultiAgentBase as an async AgentTool for delegation.
Source code in src/kaboo_workflows/tools/wrappers.py
151 152 153 154 155 156 157 158 | |
node_as_tool ¶
node_as_tool(node, *, name=None, description)
Wrap an Agent or MultiAgentBase as a sync AgentTool for delegation.
Source code in src/kaboo_workflows/tools/wrappers.py
141 142 143 144 145 146 147 148 | |
serialize_multiagent_result ¶
serialize_multiagent_result(result)
Serialize a MultiAgentResult with execution metadata omitted by to_dict().
Extends result.to_dict() with fields only available on the live object:
last_node_id— id of the truly last executing node, derived fromnode_history/execution_order(not dict insertion order).response— plain-text answer from that node, ready to use directly without any further extraction.swarm.node_history— full ordered execution trace including repeated visits (SwarmResultonly).graph.execution_order,graph.edges,graph.entry_points, and node counts (GraphResultonly).
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
result
|
MultiAgentResult
|
A live |
required |
Returns:
| Type | Description |
|---|---|
dict[str, Any]
|
A JSON-serializable dict extending |
Source code in src/kaboo_workflows/tools/extractors.py
93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 | |
cli_errors ¶
cli_errors(*, exit_code=1)
Catch unhandled exceptions and print a clean, user-friendly message.
.. warning::
**CLI-only** — this context manager calls ``sys.exit()`` on errors,
which raises ``SystemExit``. Do **not** use it in ASGI/WSGI server
code (FastAPI, Flask, etc.) where ``SystemExit`` would kill the
worker process. For server code, catch exceptions directly.
Intended for CLI entry points — wraps the body so that configuration errors, auth failures, network problems, etc. are displayed without a full Python traceback::
with cli_errors():
asyncio.run(main())
KeyboardInterrupt and SystemExit are not caught.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
exit_code
|
int
|
Process exit code used after printing the error.
Set to |
1
|
Source code in src/kaboo_workflows/utils.py
170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 | |
make_event_queue ¶
make_event_queue(
agents,
*,
orchestrators=None,
tool_labels=None,
stream_groups=None,
history=None,
entry_name=None,
chat_owner=None,
chat_reply=None,
session_id=None,
)
Attach :class:~kaboo_workflows.hooks.EventPublisher hooks to agents.
Every agent in agents receives an :class:.EventPublisher hook and a
matching callback_handler so all per-agent event types flow into the
returned :class:EventQueue. Orchestrators (Swarm / Graph / delegate
Agent) in orchestrators also get a publisher for NODE_START, NODE_STOP,
HANDOFF, and MULTIAGENT_COMPLETE events.
This function does not emit SESSION_START. Callers that own a
:class:~kaboo_workflows.types.SessionManifest should call
:meth:EventQueue.emit_session_start themselves; the common
:class:ResolvedConfig workflow does this for you via
:meth:ResolvedConfig.wire_event_queue.
.. warning::
This function **mutates** the passed-in agents and orchestrators by
adding hooks and overwriting ``callback_handler``. Call it only once
per set of agents.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
agents
|
dict[str, Agent]
|
Agents to wire, keyed by name. |
required |
orchestrators
|
dict[str, Node] | None
|
Built orchestrations keyed by name. |
None
|
tool_labels
|
dict[str, str] | None
|
Tool name → display label mapping forwarded to each
:class: |
None
|
stream_groups
|
dict[str, tuple[str, str]] | None
|
agent_name → (group, title) mapping for hierarchical activity attribution. When an agent is not in this dict it uses its own name as the group. |
None
|
history
|
dict[str, tuple[str, bool]] | None
|
agent_name → (key, enabled) mapping for client-driven
conversation history. Every agent except the chat-owning agent
receives a :class: |
None
|
entry_name
|
str | None
|
The configured name of the entry node. Stored on the
EventQueue and used as |
None
|
chat_owner
|
str | None
|
Name of the agent whose transcript IS the chat and must not
receive a |
None
|
chat_reply
|
str | None
|
Name of the agent whose streamed text becomes the chat reply
(the chat bubble). Its activity card is flagged |
None
|
session_id
|
str | None
|
The effective session id. Stored on the EventQueue and included in the SESSION_END event payload. |
None
|
Returns:
| Type | Description |
|---|---|
EventQueue
|
A ready-to-use :class: |
Source code in src/kaboo_workflows/wire.py
190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 | |