AI / Apache Burr Interview questions
1. What is Apache Burr?
Apache Burr is a lightweight, dependency-free Python framework for building applications as action-driven state machines . It standardizes how you express state, actions, and transitions so that decision-making systems — chatbots, AI agents, simulations — stay easy to follow and easy ...
2. What is the purpose of Burr’s ApplicationBuilder?
The ApplicationBuilder is the fluent builder class you use to assemble a Burr Application . You are expected to only ever instantiate ApplicationBuilder , never the Application class directly. At minimum it needs three things: the actions (via with_actions(...) ), the transitions between them (vi...
3. What are the core building blocks of a Burr application?
Every Burr application is composed of four core constructs that work together to form and run a state machine: Construct Role Action A function or class that reads from state, does the compute, and writes results back. State The immutable data that actions read from and write to, carried between ...
4. Define an Action in Burr?
An Action is the core unit of compute in Burr. It has two responsibilities: run , which computes a result, and update , which uses that result to produce the new state. Burr calls the run part a Function and the update part a Reducer — a naming borrowed loosely from Redux. Every action decl...
5. What are the two ways to define an Action in Burr?
Burr supports a function-based API and a class-based API, and they are largely equivalent. Function-based, using the @action decorator, is best for something quick that reads a fixed set of state variables: from burr.core import action, State @action (reads = [ "var_from_state" ], writes = [ "var...
6. Describe the State object in Burr?
Burr’s State is how actions talk to each other, and it is entirely immutable — you can only create a new state from an old one, never modify one in place. It extends Python’s Mapping interface for reads, so normal dictionary-style access works, plus extras like state.subset([...]) and state...
7. What are the common State mutation methods in Burr?
Because State is immutable, every write goes through one of a small set of named operations, each returning a new State : Method Effect state.update(foo=bar) Sets key foo to bar . state.append(foo=bar) Appends bar to the list stored at foo . state.increment(foo=1) Increments the numeric value at ...
8. What is a Transition in Burr?
A Transition defines how two actions are connected and which action becomes available next for a given state — conceptually, it is an edge in the state machine’s graph. Every transition has three parts: a from action, a to action, and a condition that must evaluate to true for the machine t...
9. List the built-in condition functions Burr provides for transitions?
Burr ships three convenience functions for writing transition conditions, all importable from burr.core : from burr.core import when, expr, default with_transitions( ( "from" , "to" , when(foo = "bar" )), # true when state["foo"] == "bar" ( "from" , "to" , expr( "epochs>100" )), # true when the e...
10. What is the purpose of the default condition in with_transitions?
default is a special condition that always evaluates to True . It is meant as a catch-all transition that fires when none of the more specific conditions on outgoing edges from an action match. Burr also lets you skip specifying a condition entirely: passing a plain two-item tuple like ("human_co...
11. How do you set up Burr to run its tracking UI locally?
Install Burr with the start extra, which pulls in the tracking client, tracking server, and UI dependencies: pip install "burr[start]" Then simply run the burr command from your terminal. It starts a local server on port 7241 and opens a browser window with the UI, pre-loaded with sample projects...
12. What is the Burr UI used for?
The Burr UI is a self-hostable telemetry dashboard for watching a state machine execute in real time. For every step it shows the action that ran, its input, its result, and a snapshot of state at that point — which makes it useful for both live monitoring and after-the-fact debugging. It c...
13. What are Projects, Applications, and Steps in Burr’s tracking model?
Burr’s telemetry data model has three nested levels: Level Meaning Project The top-level grouping, set as the required argument to with_tracker(project=...) . Shown as the first page in the UI. Application A single run logged under a project, similar to a "trace" in distributed tracing. Optionall...
14. How do you use runtime inputs in a Burr action?
Actions can declare parameters that live outside of state — things like an API client or a piece of human input that only exists at call time. In the function-based API, any unbound extra parameter becomes a runtime input automatically: @action (reads = [ "..." ], writes = [ "..." ]) def my...
15. What is the purpose of the bind method on a Burr action?
.bind() lets you fix a value for one of an action’s extra parameters at construction time, similar to functools.partial but more explicit and readable: @action (reads = [ "var_from_state" ], writes = [ "var_to_update" ]) def custom_action (state: State, increment_by: int) -> State: return state ....
16. Define State Persistence in Burr?
State Persistence is Burr’s core API for saving and loading application state to and from a database, so a run can be paused and later resumed exactly where it left off. Writing is enabled with with_state_persister(persister) on the ApplicationBuilder , which writes state after every action. Read...
17. What are app_id and partition_key used for in Burr?
Burr applications are keyed on two identifiers used by the persistence and tracking layers: Identifier Purpose app_id Uniquely identifies a single application run. A UUID is generated automatically if you don’t supply one. partition_key Groups related applications together, e.g. a user’s ID or em...
18. What is a Streaming Action in Burr?
A Streaming Action is an action that yields its result incrementally instead of returning it all at once — useful for showing LLM tokens to a user as they’re generated, or streaming metrics from a long-running training loop. They can be written as functions (with @streaming_action ) or clas...
19. What is Typing State in Burr?
Typing State lets you attach a schema — currently built on Pydantic — to your application’s state, so you get self-documenting actions, IDE autocompletion, and a way to inspect state shape ahead of execution. It works at the application level ( with_typing(PydanticTypingSystem(MyModel...
20. Describe what Hooks do in Burr?
Hooks are lifecycle adapters — a concept borrowed from Hamilton — that let you plug custom logic into specific points of an action’s execution without touching your actions themselves. Typical uses include logging every step to an external observability tool, adding a rendering delay,...
21. What is Parallelism used for in Burr?
Parallelism lets a single Burr action expand into many actions or subgraphs that run concurrently and are then joined back into one result — a map-reduce pattern applied to state machines. Common use cases include trying several prompts against the same LLM, trying the same prompt against s...
22. What are Recursive Applications in Burr?
A Recursive Application is a Burr application run from inside another Burr action — effectively "Burr inside Burr." This makes it possible to build agents composed of agents, black-box a complex sub-task behind a single parent action, or fan a task out to several sub-applications and join t...
23. Why is Burr’s State object immutable?
Immutability makes every state transition traceable. Because state.update(...) and its siblings always return a new State rather than mutating the old one, the docs describe it as behaving "a bit like" version control’s commit/checkout/merge cycle: before an action runs, state is subset down to o...
24. How does Burr decide which transition to follow when multiple conditions could match?
Conditions on the outgoing edges of an action are evaluated in the order they are specified in with_transitions(...) , and the first one that evaluates to True is the transition Burr takes. with_transitions( ("check", "adult", when(age__gte=18)), ("check", "child", when(age__lt=18)), ("check", "f...
25. What is the difference between step and iterate in a Burr Application?
step / astep and iterate / aiterate both run actions, but at different granularities: API Behavior step / astep Runs exactly one action and returns the tuple (action, result, state) . Best for manual, step-by-step control. iterate / aiterate Calls step repeatedly as a generator until a halt_befor...
26. What is the difference between halt_before and halt_after?
Both parameters tell run / iterate / step when to stop, but at opposite points relative to the named action: Parameter Stops Result returned halt_after After the named action has already run The action’s actual result halt_before Right before the named action would run None , since it hasn’t exec...
27. When should you use the class-based Action API instead of the function-based one?
Reach for the class-based API, subclassing Action , when you need inheritance (sharing behavior across a family of related actions) or want to parameterize an action in ways richer than a simple bound value — for example, holding configuration on self and using it across run and update . It...
28. What is the difference between binding a value to an action and passing it as a runtime input?
Both fill in a parameter an action needs beyond state, but at different times and for different reasons: Approach When set Good for action.bind(param=value) At application build time Stable dependencies: API clients, DB connections, fixed model names inputs={"param": value} At call time, per run ...
29. Why should you tag actions instead of just using their names?
Tags act as an alias that can cover multiple actions at once, which a hardcoded name can’t do. If several different actions all produce something you want to display — say, text_response and image_response — you can tag them both response_to_display and then refer to that group collec...
30. How does Burr’s state persistence resume an application at the point it left off?
You call initialize_from(persister, resume_at_next_action=True, default_entrypoint=..., default_state=...) on the ApplicationBuilder instead of with_state / with_entrypoint (those are mutually exclusive with initialize_from ). state_persister = SQLLitePersister.from_values(db_path=".sqllite.db", ...
31. What is Forking State, and when would you use it?
Forking lets a new application start from a specific point in a different, prior application’s history, without adding to or mutating that original run. It is set via three optional arguments to initialize_from : fork_from_app_id , fork_from_partition_key , and fork_from_sequence_id (the exact st...
32. Why do Burr’s persister classes follow the b_ naming prefix convention?
Persister classes are named with a b_database-dependency-library pattern (e.g. a persister built on asyncpg lives under a name reflecting that). The b_ prefix exists specifically to avoid clashing with the underlying database client library’s own class or module names when both are imported side ...
33. Why should you use a state persister as a context manager?
Persisters hold a live database connection, and that connection needs to be closed deterministically or it leaks. For synchronous persisters, Burr currently closes the connection when the persister object is garbage collected — but that is not guaranteed to happen promptly, and it is not po...
34. How does the Burr UI’s tracking client double as a state loader for local development?
LocalTrackingClient implements both sides of the persistence contract at once: it writes step-by-step telemetry to ~/.burr as your application runs (the same data the Burr UI reads), and it can also load a prior run’s state back via its .load() classmethod. tracker = LocalTrackingClient(project=p...
35. Explain the execution flow of a streaming action from the first yield to the last?
A streaming action is written as a generator, and Burr treats every yield except the last as an intermediate result, with the final yield carrying the real state update: flowchart TD A["application.stream_result() called"] --> B["Action's stream_run() starts executing"] B --> C["Chunk arrives fro...
36. What is the difference between the intermediate yields and the final yield in a streaming action?
The generator in a function-based streaming action always yields a 2-tuple, but what the second element means changes on the very last yield: Yield Tuple shape State effect Intermediate ({'response': delta}, None) None — no state update happens Final ({'response': full_response}, state.appe...
37. How do you consume results from a StreamingResultContainer?
A StreamingResultContainer behaves like a cached iterator: you loop over it to get chunks one at a time, and once it’s exhausted, call .get() to retrieve the joined final result and the updated state. action, streaming_result = application.stream_result ( halt_after = "streaming_response" , input...
38. What is the difference between application-level and action-level typing in Burr?
Both use Pydantic, but they type different scopes of your app: Scope How it’s set Benefit Application-level with_typing(PydanticTypingSystem(MyState)) + with_state(MyState()) The whole app is typed; app.state.data is a real instance of your model, giving IDE autocompletion end-to-end. Action-leve...
39. Why would you combine application-level and action-level typing in the same Burr app?
Each level solves a different problem, so using both plays to their separate strengths. Application-level typing gives you a single, complete schema that downstream consumers — a web server, another service, a test harness — can rely on, plus whole-app IDE support when you write orche...
40. Explain the lifecycle of a hook during a single Burr action execution?
Hooks attach at specific points around an action’s run, and Burr calls the matching methods on every registered hook at each point: sequenceDiagram participant App as Application participant Hook as Registered Hook(s) participant Act as Action App->>Hook: pre_run_step(state, action) Hook-->>App: ...
41. When should you write a custom hook instead of relying on with_tracker?
with_tracker(...) already gives you Burr’s own step-by-step logging and the Burr UI dashboard for free, so for standard debugging and monitoring within Burr’s own tooling, it is usually all you need. Write a custom hook when you need to route information somewhere the tracker doesn’t reach: pushi...
42. What is the difference between MapStates and MapActions in Burr’s parallelism API?
Both are higher-level parallelism helpers that expand into many sub-applications and then join the results, but they vary a different axis: Class Varies Typical use MapStates Same action, many states, via .states() One LLM, many prompts MapActions Same state, many actions, via .actions() Same pro...
43. What is MapActionsAndStates used for?
MapActionsAndStates runs the full cartesian product of a generator of actions and a generator of states — every action combined with every state — and it is actually the base class that both MapStates and MapActions are built on top of. class TestModelsOverPrompts (MapActionsAndStates...
44. How does the reduce method work in Burr’s parallel actions?
reduce(self, state, states) is the join step of Burr’s map-reduce parallelism: it receives the original state the parallel action started from, plus a generator yielding one finished sub-application State per task that ran. Its job is to fold those many states into a single new State to hand back...
45. When would you choose a RunnableGraph over a single action for a parallel branch?
Choose a RunnableGraph when the thing you want to fan out isn’t a single computation but a small multi-step subflow — for example, a "process the prompt, then call the LLM" pair of actions that should run together as one parallel branch. graph = ( GraphBuilder() . with_action(process_prompt...
46. How can you optimize parallel Burr actions using a custom executor?
Burr’s synchronous parallelism runs on a concurrent.futures.Executor , and you can control it at the application level with .with_parallel_executor(...) , which becomes the default executor passed down to every parallel action: app = ( ApplicationBuilder() .with_parallel_executor(MultiThreadedExe...
47. How does tracking get linked between a parent application and a sub-application?
Inside the parent action that spawns a sub-application, you request the ApplicationContext via the special __context parameter, then wire two things into the child’s ApplicationBuilder : a copy of the parent’s tracker, and the parent’s identifying coordinates via with_spawning_parent(...) . @acti...
48. Why is Apache Burr currently going through Apache Incubation, and what does that mean for production use?
The Apache Software Foundation requires every newly accepted project to go through incubation until its infrastructure, communications, and decision-making process have stabilized to match other established ASF projects. Burr entered incubation after Stefan Krawczyk and Elijah ben Izzy proposed d...
49. What is the difference between Apache Burr and LangGraph?
Both let you build cyclical, stateful LLM applications, but they start from different mental models: Aspect Apache Burr LangGraph Core model Explicit state machine: actions declare exact read/write contracts Directed graph of nodes passing messages/state Observability Built-in, self-hosted Burr U...
50. How do you troubleshoot a Burr application that halts without reaching a specified halt condition?
Burr documents this scenario explicitly as undefined behavior : if the state machine reaches an action that has no outgoing transition matching the current state, it halts anyway and logs a warning, even though you never told it to stop there. Inspect the static graph. Call application.graph to g...