Skip to content

What works through mount()

The integration boundary is Burr’s Application. Anything supported by ApplicationBuilder passes through mount() without adapter changes, including parallelism, persistence, telemetry, and library coexistence. Anything missing from this table has not been exercised yet.

Burr surfaceThrough mount()Demo / evidence
@action, with_transitions, with_state, with_entrypointYes (core path)every demo
Condition.expr / .when / .defaultYescoffee_order, chargen, incident_response
with_tracker(LocalTrackingClient)Yes; surfaced at theodosia://traceevery narrative demo
with_state_persister(BaseStatePersister)Yessqlite_persister
with_typed_state(Pydantic)Yes; JSON schema exported via theodosia://graph state_schematyped_state_loan
@pydantic_action decoratorYes; subset-model machinery surfaces the action’s typed slicepydantic_actions
with_identifiers(partition_key=...) (multi-tenancy)Yes; surfaces in theodosia://session.partition_keypartition_key_tenants
with_parallel_executor(...)Yes (default thread-pool); RayExecutor swap documented inlineburr_map_parallel
MapStates / parallel sub-runsYesburr_map_parallel
Streaming actionsYes; emitted as MCP progress notificationsstreaming_narrate
Async actions (async def @action)Yesparallel_research, mellea_qiskit_migration
Sub-Application compositionYes; theodosia://subruns indexes spawn_subapp callsincident_response, subgraphs
OpenTelemetry (OpenTelemetryBridge)Yeswith_otel
User-defined lifecycle hooks (PreRunStepHook / PostRunStepHook / etc.)Yes; via ApplicationBuilder.with_hooks(...)pipeline_hooks
Async hooks + envelope hooks (PreRunStepHookAsync, PostApplicationCreateHook, PreRunExecuteCallHookAsync, etc.)Yes; awaited around each action; envelope hooks wrap every execute boundary including MCP stepasync_hooks
@streaming_action.pydantic + streaming hooks (PreStartStreamHook, PostStreamItemHook, PostEndStreamHook)Yes; chunks typed by stream_type, hooks fire when streaming actions are driven via MCP step (adapter uses app.astream_result)streaming_hooks
Span tracing hooks (PreStartSpanHook, PostEndSpanHook, DoLogAttributeHook) via the __tracer parameterYes; user-defined hook captures sub-span trees and attribute logs alongside OpenTelemetryBridgecustom_telemetry, with_otel
ApplicationBuilder.initialize_from(persister, fork_from_app_id=..., fork_from_sequence_id=...) (builder-level state forking)Yes; two Applications share an initial state via a persister, then walk independently with their own uidsstate_forking, sqlite_persister
AsyncBaseStatePersister + PersisterHookAsyncYes; await persister.save(...) runs inline on the MCP step path (adapter drives astep, hooks fire async)async_persister
@trace decorator (auto-span any function called from an action)Yes; nested call graph maps onto the span tree, inputs/outputs auto-logged as attributestrace_decorator
Burr’s prebuilt StateAndResultsFullLogger (zero-config JSONL audit log)Yes; one JSONL row per action with post-step state + result + timingfull_logger
FastMCP ctx.sample from inside an action bodyYes; theodosia.current_mcp_context() returns the FastMCP Context so actions can delegate LLM work to the connected agent’s modelcaller_sample
FastMCP ctx.elicit from inside an action bodyYes; action bodies can pop interactive user confirmation prompts mid-step for safety-rail gateselicit_confirm
Output schema on the step toolYes; clients see a typed response contract (discriminator error + per-shape fields) in the MCP tool listingalways-on
FastMCP middleware (timing, structured logging, rate limiting, custom)Yes; mounted server is a regular FastMCP server, so server.add_middleware(...) after mount(...) workswith_middleware
with_graph(Graph) / with_graphs(...) (reusable graph fragments)Yes; same Graph object embedded in multiple Applicationssubgraph_composition
Class-based Action subclasses (escape from @action)Yes; one class, configured instancesclass_action
Hamilton driver inside an action bodyYes (no special integration)hamilton_features
app.run(halt_after=...) auto-routingBurr-level onlyMCP path always uses agent-chosen actions via step

If the starting point is a flat FastMCP server rather than a Burr graph, burr_app_from_fastmcp(...) lifts the tools into an Application so they gain transition enforcement and an audit trail. This path is less exercised than building a graph directly; treat it as advanced.

from fastmcp import FastMCP
from theodosia import ServingMode, ToolSpec, burr_app_from_fastmcp, mount
flat = FastMCP("legacy")
@flat.tool
async def create_order(item: str) -> dict:
return {"order_id": "ORD-1", "item": item}
@flat.tool
async def pay(order_id: str, amount: float) -> dict:
return {"paid": True, "receipt": "R-99"}
@flat.tool
async def fulfill(order_id: str) -> dict:
return {"status": "fulfilled"}
app = await burr_app_from_fastmcp(
flat,
entrypoint="create_order",
initial_state={"order_id": None, "paid": False},
tool_specs={
"create_order": ToolSpec(writes=["order_id"], merge_result=True),
"pay": ToolSpec(reads=["order_id"], writes=["paid"], merge_result=True),
"fulfill": ToolSpec(reads=["order_id", "paid"]),
},
transitions=[("create_order", "pay"), ("pay", "fulfill")],
)
server = mount(app, mode=ServingMode.STEP, name="lifted")

You declare the state machine explicitly because parameter names do not tell you which tools mutate shared state. What carries over without declaration: parameter names, types, defaults, docstrings, and async/sync nature. ToolSpec also accepts state_update (a callable from result to state mutations) and rename (change the action’s name in the graph). tests/test_importing.py exercises every knob.