Skip to content

Concepts

A mental model for how kaboo-workflows turns a YAML file into a running, streaming multi-agent system.

load() returns plain strands objects

kaboo-workflows is a resolver, not a runtime wrapper. load(config) reads your YAML, resolves every reference, and returns live strands-agents objects — strands.Agent, Swarm, Graph — with nothing wrapping them. Whatever you can do with a hand-built strands object, you can do with the one kaboo hands back.

flowchart LR
    YAML["config.yaml"] -->|load| Resolved["ResolvedConfig"]
    Resolved --> Entry["resolved.entry<br/>(strands Agent / Swarm / Graph)"]
    Entry -->|"kaboo-serve"| SSE["AG-UI SSE endpoint"]
    SSE -->|"Server-Sent Events"| CK["CopilotKit frontend"]

The pieces

A config wires together a handful of resolvable sections (each has its own chapter in the Configuration reference):

Section What it is
models Named model providers (bedrock, openai, ollama, gemini).
agents Named agents: a model, a system prompt, tools, hooks and MCP clients.
mcp_servers MCP servers the library starts and stops for you.
mcp_clients Connections to MCP servers, which agents attach by name.
orchestrations How agents compose into a multi-agent system.
attachments How references (files + custom entities cited via @) reach agents.
session_manager Where conversation state persists between runs.
entry The root agent or orchestration a run starts from. The only required key.

Tools and hooks are not top-level sections — they are declared per agent, as agents.<name>.tools and agents.<name>.hooks. Tools are Python @tool functions loaded from files or modules; tools the browser runs, such as ask_user, arrive with each run and reach the agents whose client_tools name them. Hooks are lifecycle callbacks for event publishing, interrupts and guards. state_effects write tool results into the shared state. The remaining root keys are settings rather than wiring: history, telemetry, runtime, log_level and version, plus vars for interpolation.

Orchestration shapes

Agents compose through orchestrations, which nest arbitrarily:

  • delegate — an entry agent calls other agents as tools (see deep nesting).
  • swarm — agents hand off to one another until one produces the answer.
  • graph — an explicit DAG of agents with edges and an entry point.

Those three are the whole list — mode: accepts nothing else. Two patterns people look for as modes are properties of a graph rather than shapes of their own:

  • parallel — a graph node with several outgoing edges fans out, and the branches run concurrently (see parallel).
  • nested — any orchestration can be a node inside another (see swarm + graph).

Serving: the AG-UI event stream

kaboo-serve (or create_agui_app in kaboo_workflows.adapters) wraps the resolved entry with ag-ui-strands and exposes it as an AG-UI 1.0 SSE endpoint. A run uses the protocol's own events throughout:

RUN_STARTED → STATE_SNAPSHOT → TEXT_MESSAGE_* / TOOL_CALL_* → SUBAGENT_STARTED → (tagged events) → SUBAGENT_FINISHED → STATE_DELTA → RUN_FINISHED
  • Sub-agents are SUBAGENT_* events, with their text, reasoning and tool calls tagged by subagentRunId, which kaboo-react folds into cards (Sub-agents in a chat UI).
  • Pauses end the run with RUN_FINISHED: waiting on a tool the browser runs (pendingToolCallIds), or on approvals (an interrupt outcome) — see human-in-the-loop.
  • State changes — the conversation's sessions and the shared state — leave as STATE_DELTAs.
  • Usage on RUN_FINISHED counts every agent of the run.

kaboo-runtime persists the stream and replays it on reconnect. GET /invocations/capabilities describes what the service supports as AG-UI AgentCapabilities.

Response size

A run streams only what it adds: its text, its tool calls and their results, its sub-agents' events and its state changes. It never re-sends the conversation. ag-ui-strands' MESSAGES_SNAPSHOT, which repeats the whole transcript after every step, is turned off, because with it a response grows with the length of the thread times the steps of the turn, and a long thread eventually reaches a host's response cap (AWS AgentCore closes the socket at 100 MiB). Clients build the transcript from the events, as CopilotKit does, and send it back with the next turn.

Measured in the lab, a deliberately long turn (twenty SQL results of up to 200 rows, a 63-component A2UI surface, a 40 KB generated visual and a dashboard of twelve widgets) sends about 230 KB, and every turn of a twelve-turn thread sends the same 28 KB whatever came before it.

References & attachments

Anything a user cites from the frontend with @ — an uploaded file or a pointer to a custom entity (a table, a dashboard) — is a reference. The AG-UI layer gives inline media to the entry agent automatically; the attachments: config extends that to any agent in a pipeline via a lightweight manifest, an on-demand tool (fetch_attachment / list_references), and opt-in inline ContentBlocks for vision/doc-capable models. File attachments are resolved by the built-in tool; custom object kinds are resolved by your own MCP tool. See Attachments & Multimodal.

Attachment URL fetches funnel through a pluggable fetcher: configure attachments.base_url / authorization for files behind your API's auth, or register a ReferenceFetcher in code for multi-store routing.

The host side channel: forwardedProps

The AG-UI protocol carries a free-form forwardedProps object on every run — context the host backend sends the runtime that is not part of the conversation and never reaches the model as text: run-scoped credentials, tenant ids, per-run agent configuration. kaboo binds it per request; your tools and hooks read it back with get_forwarded_props(), and every agent also receives a copy in its state under forwarded_props.

Two built-in features consume it:

  • Authorized attachment fetching — attachments.authorization: forwarded_props:<key> reads a run-scoped bearer token for own-origin fetches.
  • Runtime-submitted workflow configs — with create_agui_app(session_config_key="workflow_config"), the run's own YAML arrives under that key and is merged over the service's base config. This is how one process serves many host-defined agent types: each run gets its own agents, orchestration, entry and MCP client sessions, and editing an agent type takes effect on the next turn without a restart. See Chapter 13.

A submitted config supersedes forwardedProps.agent_config, which applied only system_prompt and model_id behind runtime.allow_invocation_overrides. That flag is deprecated: an overlay expresses both, plus the structure they could not.

Everything on this channel is only as trustworthy as whoever set it, so a host should stamp these server-side rather than accept them from a browser — see server-side props in kaboo-runtime for where that goes on a Node host.

Statelessness: what a run keeps and what it carries

A run keeps nothing. Everything a conversation accumulates arrives with the turn on the AG-UI state channel, under kaboo_sessions: every agent's transcript (history), the paused checkpoint of a run waiting on the person (paused), and the approvals it waits on (approvals). The run writes it back as one STATE_DELTA, and the host persists it. kaboo-runtime keeps the key on the server, so a browser never sees or sends it.

That is what makes the rest safe: a service can rebuild its agents every run, scale to a second replica, or restart mid-approval, and behave identically, because the objects being rebuilt hold behaviour rather than memory.

Where to go next