From 68cac4041b646fc73ced172d03b5361bb41ede48 Mon Sep 17 00:00:00 2001 From: Stefan Krawczyk Date: Sat, 31 Jan 2026 10:51:14 -0800 Subject: [PATCH 1/7] Add Claude Code skill for Burr This adds a comprehensive Claude Code skill that helps developers build applications with Apache Burr. When active in Claude Code, it provides: - Expert guidance on Burr APIs and patterns - Code generation for actions, state machines, and applications - Code review for best practices - Debugging assistance - Working examples for common patterns The skill includes: - SKILL.md: Main skill instructions for Claude - api-reference.md: Complete API documentation - examples.md: Working code examples - patterns.md: Best practices and design patterns - troubleshooting.md: Common issues and solutions - README.md: Installation and usage guide - plugin.json: Plugin manifest for easy installation Easy installation via Claude CLI: claude skill install https://github.com/apache/burr/.claude/skills/burr Or manual install by copying to ~/.claude/skills/burr/ for personal use or .claude/skills/burr/ for project-specific use. Documentation added at: - docs/getting_started/claude-skill.rst: Full usage guide - CLAUDE_SKILL.md: Quick start guide - Main README updated with installation instructions Contributions welcome via issues or pull requests! --- .claude/skills/burr/README.md | 308 +++++++++++ .claude/skills/burr/SKILL.md | 212 +++++++ .claude/skills/burr/api-reference.md | 567 +++++++++++++++++++ .claude/skills/burr/examples.md | 629 +++++++++++++++++++++ .claude/skills/burr/patterns.md | 668 ++++++++++++++++++++++ .claude/skills/burr/plugin.json | 34 ++ .claude/skills/burr/troubleshooting.md | 732 +++++++++++++++++++++++++ .rat-excludes | 3 + CLAUDE_SKILL.md | 168 ++++++ README.md | 16 + docs/getting_started/claude-skill.rst | 439 +++++++++++++++ docs/getting_started/index.rst | 1 + 12 files changed, 3777 insertions(+) create mode 100644 .claude/skills/burr/README.md create mode 100644 .claude/skills/burr/SKILL.md create mode 100644 .claude/skills/burr/api-reference.md create mode 100644 .claude/skills/burr/examples.md create mode 100644 .claude/skills/burr/patterns.md create mode 100644 .claude/skills/burr/plugin.json create mode 100644 .claude/skills/burr/troubleshooting.md create mode 100644 CLAUDE_SKILL.md create mode 100644 docs/getting_started/claude-skill.rst diff --git a/.claude/skills/burr/README.md b/.claude/skills/burr/README.md new file mode 100644 index 000000000..2b79fbd78 --- /dev/null +++ b/.claude/skills/burr/README.md @@ -0,0 +1,308 @@ + + +# Apache Burr Claude Skill + +A comprehensive Claude Code skill for building stateful applications with Apache Burr. + +## What is this? + +This is a Claude Code skill that teaches Claude how to help you build applications using Apache Burr. When active, Claude becomes an expert in Burr's APIs, best practices, and common patterns. + +## Installation + +### Option 1: Install from GitHub (Easiest) + +```bash +# Install to personal skills directory +claude skill install https://github.com/apache/burr/.claude/skills/burr + +# Or install to your current project +claude skill install https://github.com/apache/burr/.claude/skills/burr --project +``` + +### Option 2: Manual Install - Project-level (For teams) + +Copy this skill to your project's `.claude/skills/` directory: + +```bash +# From your project root +cp -r /path/to/burr/.claude/skills/burr .claude/skills/ + +# Or clone and copy from GitHub +git clone https://github.com/apache/burr +cp -r burr/.claude/skills/burr .claude/skills/ +``` + +### Option 3: Manual Install - Personal scope (For individual use) + +Copy to your personal Claude skills directory: + +```bash +# Copy to personal skills directory +cp -r /path/to/burr/.claude/skills/burr ~/.claude/skills/ + +# Or clone and copy from GitHub +git clone https://github.com/apache/burr +cp -r burr/.claude/skills/burr ~/.claude/skills/ +``` + +### Verify Installation + +The skill should now appear in Claude Code's skill menu: + +```bash +/burr --help +``` + +Or just ask Claude naturally: +``` +"Help me build a Burr application for a chatbot" +``` + +## What Can It Do? + +This skill helps you: + +- **Build new Burr applications** - Get help scaffolding state machines +- **Write actions** - Create properly structured action functions +- **Define transitions** - Set up conditional and default transitions +- **Add observability** - Configure tracking and the Burr UI +- **Debug issues** - Troubleshoot common problems +- **Follow best practices** - Learn recommended patterns +- **Review code** - Get feedback on your Burr applications + +## Usage Examples + +### Manual Invocation + +Explicitly invoke the skill with the `/burr` command: + +``` +/burr How do I create a streaming action? + +/burr Review this action for best practices + +/burr Help me add state persistence to my app +``` + +### Automatic Invocation + +Claude will automatically load the skill when it detects you're working with Burr: + +``` +"I'm building a chatbot with Burr and need help with the state machine" + +"Why isn't my action updating the state?" + +"Show me an example of parallel execution in Burr" +``` + +## What's Included + +The skill includes: + +- **SKILL.md** - Main skill instructions for Claude +- **api-reference.md** - Complete API documentation +- **examples.md** - Working code examples for common patterns +- **patterns.md** - Best practices and design patterns +- **troubleshooting.md** - Solutions to common issues + +Claude will reference these files to provide accurate, helpful guidance. + +## Features + +### Code Generation + +``` +"Create a Burr application that processes user queries with RAG" +``` + +Claude will generate a complete application with actions, transitions, and tracking. + +### Code Review + +``` +"Review my Burr application for best practices" +``` + +Claude will check for: +- Correct `reads` and `writes` declarations +- Proper state immutability +- Complete transition coverage +- Tracking configuration +- Error handling + +### Debugging Help + +``` +"My state machine is looping infinitely, what's wrong?" +``` + +Claude will: +- Analyze your transitions +- Suggest using `.visualize()` to see the graph +- Recommend fixes based on common issues + +### Learning & Examples + +``` +"Show me how to implement retries in Burr" +``` + +Claude will provide working examples from the examples.md reference. + +## Skill Configuration + +### Allowed Tools + +The skill permits Claude to use: +- `Read` - Read your code files +- `Grep` - Search for patterns +- `Glob` - Find files +- `Bash` - Run Python, burr CLI, and pip commands + +### Automatic Activation + +The skill activates when you: +- Mention "Burr" or "Apache Burr" +- Show code with `from burr.core import` +- Ask about state machines or actions +- Need help with stateful applications + +## Tips for Best Results + +1. **Be specific** - "Help me add retry logic to my fetch action" is better than "help with errors" + +2. **Show your code** - Claude works best when it can see what you're working with + +3. **Ask for examples** - "Show me an example of..." gets working code + +4. **Reference the docs** - Ask Claude to check the API reference or patterns guide + +5. **Use visualization** - Ask Claude to suggest using `app.visualize()` when debugging + +## Examples of What to Ask + +### Getting Started +- "Help me create my first Burr application" +- "What's the basic structure of a Burr action?" +- "Show me a simple chatbot example" + +### Building Features +- "How do I add streaming to my LLM action?" +- "Show me how to implement parallel execution" +- "Help me add state persistence with SQLite" + +### Debugging +- "Why isn't my transition working?" +- "My action isn't updating state, what's wrong?" +- "How do I debug an infinite loop?" + +### Best Practices +- "Review this code for Burr best practices" +- "Is this the right way to structure my state machine?" +- "How should I handle errors in actions?" + +## Updating the Skill + +To get the latest version: + +```bash +cd /path/to/burr +git pull +cp -r .claude/skills/burr ~/.claude/skills/ +``` + +## Integration with Burr Project + +If you're working in the Burr repository itself, the skill is already available at `.claude/skills/burr/`. + +## Contributing + +Found an issue or want to improve the skill? We welcome contributions! + +### Reporting Issues + +If you find a bug or have a suggestion: + +1. Check existing issues: https://github.com/apache/burr/issues +2. Open a new issue with: + - Clear description of the problem or suggestion + - Steps to reproduce (for bugs) + - Expected vs actual behavior + - Burr version and environment details + +### Contributing Improvements + +We especially appreciate pull requests! To contribute: + +1. Fork the repository +2. Edit the skill files in `.claude/skills/burr/` +3. Test your changes with Claude Code +4. Submit a PR to https://github.com/apache/burr with: + - Clear description of what you changed and why + - Examples showing the improvement + - Any relevant issue references + +Small fixes like typos, improved examples, or clearer explanations are always welcome! + +## Related Resources + +- **Burr Documentation**: https://burr.apache.org/ +- **GitHub**: https://github.com/apache/burr +- **Examples**: See `examples/` directory in the Burr repository +- **Discord**: https://discord.gg/6Zy2DwP4f3 + +## FAQ + +**Q: Do I need Burr installed to use this skill?** + +A: No, but Claude can help you install it: `pip install "burr[start]"` + +**Q: Can I customize the skill?** + +A: Yes! Edit the files in `.claude/skills/burr/` to customize behavior, add your own examples, or modify the API reference. + +**Q: Will this work with older versions of Burr?** + +A: This skill is designed for current Burr versions. Some APIs may differ in older versions. + +**Q: Can I use this skill with other frameworks?** + +A: Yes! Burr integrates well with LangChain, LlamaIndex, Apache Hamilton, and other frameworks. The skill includes integration guidance. + +**Q: How do I disable the skill temporarily?** + +A: Rename the skill directory or remove it from `.claude/skills/`: +```bash +mv ~/.claude/skills/burr ~/.claude/skills/burr.disabled +``` + +## License + +This skill is part of Apache Burr (incubating) and is licensed under the Apache License 2.0. + +See the [LICENSE](../../../LICENSE) file in the root of the repository. + +--- + +Built with ❤️ by the Burr community. + +For help, join our [Discord](https://discord.gg/6Zy2DwP4f3) or open an issue on [GitHub](https://github.com/apache/burr/issues). diff --git a/.claude/skills/burr/SKILL.md b/.claude/skills/burr/SKILL.md new file mode 100644 index 000000000..ba10c1c09 --- /dev/null +++ b/.claude/skills/burr/SKILL.md @@ -0,0 +1,212 @@ + + +--- +name: burr +description: Helps developers build stateful applications using Apache Burr, including state machines, actions, transitions, and observability +argument-hint: [action-or-concept] +allowed-tools: Read, Grep, Glob, Bash(python *, burr, pip *) +--- + +# Apache Burr Development Assistant + +You are an expert in Apache Burr (incubating), a Python framework for building stateful applications using state machines. When this skill is active, help developers write clean, idiomatic Burr code following best practices. + +## Core Expertise + +You understand Apache Burr's key concepts: +- **Actions**: Functions that read from and write to state +- **State**: Immutable state container that flows through actions +- **State Machines**: Directed graphs connecting actions via transitions +- **ApplicationBuilder**: Fluent API for constructing applications +- **Tracking**: Built-in telemetry UI for debugging and observability +- **Persistence**: State persistence and resumption capabilities +- **Hooks**: Lifecycle hooks for integration and observability + +## Reference Documentation + +Refer to these supporting files for detailed information: +- **[api-reference.md](api-reference.md)**: Complete API documentation +- **[examples.md](examples.md)**: Common patterns and working examples +- **[patterns.md](patterns.md)**: Best practices and architectural guidance +- **[troubleshooting.md](troubleshooting.md)**: Common issues and solutions + +## When Helping Developers + +### 1. Building New Applications + +When users want to create a Burr application: + +1. **Start with actions** - Define `@action` decorated functions +2. **Use ApplicationBuilder** - Follow the builder pattern +3. **Define transitions** - Connect actions with conditions +4. **Add tracking** - Enable the telemetry UI from the start +5. **Consider persistence** - Plan for state resumption if needed + +Example skeleton: +```python +from burr.core import action, State, ApplicationBuilder, default + +@action(reads=["input_key"], writes=["output_key"]) +def my_action(state: State) -> State: + # Your logic here + result = process(state["input_key"]) + return state.update(output_key=result) + +app = ( + ApplicationBuilder() + .with_actions(my_action) + .with_transitions(("my_action", "next_action", default)) + .with_state(input_key="initial_value") + .with_entrypoint("my_action") + .with_tracker("local", project="my_project") + .build() +) + +result = app.run(halt_after=["next_action"]) +``` + +### 2. Reviewing Burr Code + +When reviewing code: +- ✅ Check that actions declare correct `reads` and `writes` +- ✅ Verify state updates use `.update()` or `.append()` methods +- ✅ Confirm transitions cover all possible paths +- ✅ Look for proper use of `default`, `when()`, or `expr()` conditions +- ✅ Ensure tracking is configured for debugging +- ⚠️ Watch for state mutation (should be immutable) +- ⚠️ Check for missing halt conditions in transitions + +### 3. Explaining Concepts + +When explaining Burr features: +- Use concrete examples from [examples.md](examples.md) +- Reference the appropriate section in [api-reference.md](api-reference.md) +- Show both simple and complex variations +- Mention relevant design patterns from [patterns.md](patterns.md) +- Link to official documentation at https://burr.apache.org/ + +### 4. Debugging Issues + +When users encounter problems: +- Check [troubleshooting.md](troubleshooting.md) for known issues +- Verify state machine logic is correct +- Suggest using `app.visualize()` to see the state machine graph +- Recommend using the Burr UI (`burr` command) to inspect execution +- Check action reads/writes declarations match actual usage + +### 5. Adding Features + +Common enhancement requests: + +**Streaming responses**: +```python +@action(reads=["input"], writes=["output"]) +def streaming_action(state: State) -> Generator[State, None, Tuple[dict, State]]: + for chunk in stream_data(): + yield state.update(current_chunk=chunk) + result = {"output": final_result} + return result, state.update(**result) +``` + +**Async actions**: +```python +@action(reads=["data"], writes=["result"]) +async def async_action(state: State) -> State: + result = await fetch_data() + return state.update(result=result) +``` + +**Parallel execution**: +```python +from burr.core import graph + +graph = ( + graph.GraphBuilder() + .with_actions(action1, action2, action3) + .with_transitions( + ("start", "action1"), + ("start", "action2"), # These run in parallel + (["action1", "action2"], "action3") + ) + .build() +) +``` + +## Code Quality Standards + +When writing or reviewing Burr code: + +1. **Type annotations**: Always use type hints for state and action parameters +2. **Action purity**: Actions should be deterministic given the same state +3. **State immutability**: Never mutate state directly, always use `.update()` or `.append()` +4. **Clear naming**: Action names should be verbs describing what they do +5. **Proper reads/writes**: Declare exactly what each action reads and writes +6. **Error handling**: Use try/except in actions and update state with error info +7. **Testing**: Write tests that verify state transitions and action outputs + +## Common Patterns to Recommend + +- **Conditional branching**: Use `when(key=value)` or `expr("key > 10")` +- **Loops**: Use recursive transitions with conditions +- **Error handling**: Create error actions and transition to them on failure +- **Multi-step workflows**: Chain actions with clear single responsibilities +- **State persistence**: Use `SQLLitePersister` or `initialize_from` for resumability +- **Observability**: Always include `.with_tracker()` for the Burr UI + +## Integration Scenarios + +Burr works well with: +- **LLM frameworks**: OpenAI, Anthropic, Langchain, LlamaIndex +- **Apache Hamilton**: For DAG execution within actions +- **Streaming**: Streamlit, FastAPI, gradio for UI +- **Observability**: Langsmith, Weights & Biases, OpenTelemetry +- **Storage**: SQLite, PostgreSQL, custom persisters + +## Commands You Can Suggest + +- `burr` - Launch the telemetry UI +- `pip install "burr[start]"` - Install with UI dependencies +- `app.visualize(output_file_path="graph.png")` - Generate state machine diagram +- `python examples/hello-world-counter/application.py` - Run example + +## Key Principles + +1. **State machines make complex logic simple** - Encourage users to think in terms of states and transitions +2. **Observability is built-in** - Always recommend using the tracking UI +3. **Framework agnostic** - Burr doesn't dictate how to build models or query APIs +4. **Testability first** - Actions are pure functions that are easy to test +5. **Production ready** - Persistence, hooks, and tracking enable production deployment + +## Response Style + +- Be concise and code-focused +- Show working examples from the repository when possible +- Reference specific files in the codebase (e.g., `examples/multi-modal-chatbot/application.py`) +- Suggest running examples to learn patterns +- Point to the official docs for deep dives + +## If You Need More Context + +- Read example code from `examples/` directory +- Check the source code in `burr/core/` for implementation details +- Look at tests in `tests/` for usage patterns +- Reference official documentation at https://burr.apache.org/ + +Remember: Burr is about making stateful applications easy to build, understand, and debug. Focus on clear state machines and leverage the built-in observability tools. diff --git a/.claude/skills/burr/api-reference.md b/.claude/skills/burr/api-reference.md new file mode 100644 index 000000000..8ade6924f --- /dev/null +++ b/.claude/skills/burr/api-reference.md @@ -0,0 +1,567 @@ + + +# Apache Burr API Reference + +This is a quick reference for the most commonly used Burr APIs. For complete documentation, see https://burr.apache.org/ + +## Core Imports + +```python +from burr.core import action, State, ApplicationBuilder, default, when, expr +from burr.core.action import Action +from burr.core.application import Application +from burr.core import graph +``` + +## Actions + +### @action Decorator + +The `@action` decorator converts a function into a Burr action. + +```python +@action(reads=["key1", "key2"], writes=["result"]) +def my_action(state: State, param: str) -> State: + """Action that reads from state and returns updated state.""" + value = state["key1"] + state["key2"] + return state.update(result=value + param) +``` + +**Parameters:** +- `reads`: List of state keys this action reads from +- `writes`: List of state keys this action writes to + +**Action Function Signature:** +- First parameter: `state: State` +- Additional parameters: Runtime inputs (passed via `inputs` in `app.run()`) +- Return: Updated `State` object + +### Streaming Actions + +Actions can stream intermediate results using generators: + +```python +@action(reads=["input"], writes=["output"]) +def streaming_action(state: State) -> Generator[State, None, Tuple[dict, State]]: + """Action that yields intermediate states.""" + for i in range(10): + # Yield intermediate states + yield state.update(progress=i) + + # Return final result and state + result = {"output": "done"} + return result, state.update(**result) +``` + +### Async Actions + +Actions can be async: + +```python +@action(reads=["url"], writes=["data"]) +async def fetch_data(state: State) -> State: + """Async action for I/O-bound operations.""" + async with httpx.AsyncClient() as client: + response = await client.get(state["url"]) + data = response.json() + return state.update(data=data) +``` + +### Action Methods + +Actions can be bound with default parameters: + +```python +# Define a reusable action +@action(reads=["prompt"], writes=["response"]) +def llm_call(state: State, system_prompt: str, model: str) -> State: + response = call_llm(state["prompt"], system_prompt, model) + return state.update(response=response) + +# Bind with different parameters +answer_action = llm_call.bind( + system_prompt="Answer questions", + model="gpt-4" +) +summarize_action = llm_call.bind( + system_prompt="Summarize text", + model="gpt-3.5-turbo" +) +``` + +## State + +The `State` object is an immutable container for application state. + +### Creating State + +```python +from burr.core import State + +state = State({"counter": 0, "messages": []}) +``` + +### Accessing State + +```python +# Dictionary-style access +value = state["key"] + +# Get with default +value = state.get("key", default_value) + +# Check if key exists +if "key" in state: + pass +``` + +### Updating State + +State is immutable. Use these methods to create updated copies: + +```python +# Update single or multiple keys +new_state = state.update(counter=5, name="Alice") + +# Append to a list +new_state = state.append(messages={"role": "user", "content": "hello"}) + +# Wipe state (keep only specified keys) +new_state = state.wipe(keep=["counter"]) + +# Update with dictionary +new_state = state.update(**{"key": "value"}) +``` + +### State Methods + +- `.update(**kwargs) -> State`: Update one or more keys +- `.append(**kwargs) -> State`: Append to list values +- `.wipe(keep: List[str] = None, delete: List[str] = None) -> State`: Remove keys +- `.get(key, default=None) -> Any`: Get value with default +- `.subset(*keys) -> State`: Create new state with only specified keys + +## ApplicationBuilder + +Fluent API for building Burr applications. + +### Basic Pattern + +```python +app = ( + ApplicationBuilder() + .with_actions( + action1=my_action, + action2=another_action + ) + .with_transitions( + ("action1", "action2", default), + ("action2", "action1", when(should_loop=True)) + ) + .with_state(initial_key="value") + .with_entrypoint("action1") + .build() +) +``` + +### ApplicationBuilder Methods + +**Core building blocks:** + +- `.with_actions(**actions: Action)` - Register actions +- `.with_transitions(*transitions: Tuple)` - Define state machine transitions +- `.with_state(**state: Any)` - Set initial state +- `.with_entrypoint(action: str)` - Set starting action +- `.build() -> Application` - Construct the application + +**Observability & Tracking:** + +- `.with_tracker(tracker_type: str, project: str, **params)` - Enable tracking + - `tracker_type="local"` - Local filesystem tracking (launches UI) + - `project` - Project name in the UI + - `params={"storage_dir": "~/.burr"}` - Storage location + +**Identity & Persistence:** + +- `.with_identifiers(app_id: str, partition_key: str)` - Set app identifiers +- `.with_state_persister(persister: StatePersister)` - Enable state persistence +- `.initialize_from(persister, resume_at_next_action=True, default_state={}, default_entrypoint=None)` - Load from persister + +**Lifecycle & Hooks:** + +- `.with_hooks(*hooks: LifecycleAdapter)` - Add lifecycle hooks +- `.with_typing(typing_system: TypingSystem)` - Add type validation + +**Graph-based construction:** + +- `.with_graph(graph: Graph)` - Use pre-built graph instead of actions+transitions + +### Using Pre-built Graphs + +```python +from burr.core import graph + +g = ( + graph.GraphBuilder() + .with_actions(action1, action2, action3) + .with_transitions( + ("action1", "action2"), + ("action2", "action3") + ) + .build() +) + +app = ( + ApplicationBuilder() + .with_graph(g) + .with_state(key="value") + .with_entrypoint("action1") + .build() +) +``` + +## Transitions + +Transitions define how the state machine moves between actions. + +### Basic Transition + +```python +("source_action", "target_action") +``` + +### Conditional Transitions + +**Using `default`** - Matches if no other condition matches: +```python +from burr.core import default + +("action1", "action2", default) +``` + +**Using `when()`** - Match based on state values: +```python +from burr.core import when + +("check_age", "adult_path", when(age__gte=18)) +("check_age", "child_path", when(age__lt=18)) +``` + +**Condition operators:** +- `key=value` - Exact match +- `key__eq=value` - Explicit equality +- `key__ne=value` - Not equal +- `key__lt=value` - Less than +- `key__lte=value` - Less than or equal +- `key__gt=value` - Greater than +- `key__gte=value` - Greater than or equal +- `key__in=[values]` - In list +- `key__contains=value` - List contains value + +**Using `expr()`** - Arbitrary Python expressions: +```python +from burr.core import expr + +("counter", "counter", expr("counter < 10")) +("counter", "done", default) +``` + +### Multi-source/target Transitions + +Transition from multiple sources to multiple targets: + +```python +# Multiple sources to one target +(["action1", "action2"], "action3") + +# One source to multiple targets (for parallelism) +("start", ["parallel1", "parallel2"]) +``` + +## Application + +The built application instance provides methods to execute and inspect the state machine. + +### Running Applications + +**Basic execution:** +```python +action, result, state = app.run(halt_after=["action_name"]) +``` + +**With inputs:** +```python +action, result, state = app.run( + halt_after=["action_name"], + inputs={"param1": "value1"} +) +``` + +**Async execution:** +```python +action, result, state = await app.arun(halt_after=["action_name"]) +``` + +**Iterate through execution:** +```python +for action, result, state in app.iterate(halt_after=["end_action"]): + print(f"Executed {action.name}, result: {result}") +``` + +**Stream results:** +```python +for state in app.stream_result(halt_after=["end_action"]): + print(f"Current state: {state}") +``` + +### Application Properties + +- `app.state` - Current state +- `app.graph` - State machine graph +- `app.uid` - Unique application identifier + +### Visualization + +```python +# Generate state machine diagram +app.visualize( + output_file_path="statemachine.png", + include_conditions=True, + view=True, # Auto-open the file + format="png" # or "pdf", "svg" +) +``` + +## Persistence + +### Built-in Persisters + +**SQLite Persister:** +```python +from burr.core.persistence import SQLLitePersister + +persister = SQLLitePersister( + db_path="app.db", + table_name="burr_state", + connect_kwargs={"check_same_thread": False} +) +persister.initialize() +``` + +**Using with ApplicationBuilder:** +```python +app = ( + ApplicationBuilder() + .with_actions(...) + .with_transitions(...) + .with_identifiers(app_id="my-app", partition_key="user-123") + .with_state_persister(persister) + .initialize_from( + persister, + resume_at_next_action=True, + default_state={"counter": 0}, + default_entrypoint="start" + ) + .build() +) +``` + +### Custom Persisters + +Implement `BaseStatePersister` interface: + +```python +from burr.core.persistence import BaseStatePersister + +class CustomPersister(BaseStatePersister): + def list_app_ids(self, partition_key: str, **kwargs) -> list[str]: + """List all app IDs for a partition.""" + pass + + def load(self, partition_key: str, app_id: str, **kwargs) -> dict: + """Load persisted state.""" + pass + + def save(self, partition_key: str, app_id: str, + state: State, **kwargs) -> dict: + """Save state.""" + pass +``` + +## Tracking & Telemetry + +### Local Tracking + +```python +from burr.tracking import LocalTrackingClient + +tracker = LocalTrackingClient( + project="my_project", + storage_dir="~/.burr" +) + +app = ( + ApplicationBuilder() + .with_actions(...) + .with_tracker(tracker) + .build() +) +``` + +**Launch UI:** +```bash +burr +``` + +### Tracking Integrations + +- **Langsmith**: `from burr.integrations.langsmith import LangsmithTracker` +- **Weights & Biases**: `from burr.integrations.wandb import WandbTracker` +- **OpenTelemetry**: Built-in support for OTEL tracing + +## Hooks + +Hooks provide lifecycle callbacks for observability and integration. + +### Available Hooks + +```python +from burr.lifecycle import LifecycleAdapter + +class PrePostActionHookAsync(LifecycleAdapter): + async def pre_run_step(self, action: Action, **kwargs): + """Called before each action.""" + pass + + async def post_run_step(self, action: Action, result: dict, state: State, **kwargs): + """Called after each action.""" + pass +``` + +### Common Hook Use Cases + +- Logging and monitoring +- Performance tracking +- External system integration +- State validation +- Debugging and inspection + +## Common Helper Functions + +### Result Action + +Special action that returns a result and halts: + +```python +from burr.core import Result + +app = ( + ApplicationBuilder() + .with_actions( + compute=my_action, + result=Result("output_key") + ) + .with_transitions( + ("compute", "result") + ) + .build() +) +``` + +### Input Action + +Special action that captures runtime inputs: + +```python +from burr.core import Input + +app = ( + ApplicationBuilder() + .with_actions( + get_input=Input("user_prompt"), + process=my_action + ) + .with_transitions( + ("get_input", "process") + ) + .build() +) + +app.run(halt_after=["process"], inputs={"user_prompt": "Hello"}) +``` + +## Type Safety + +### Pydantic Integration + +```python +from burr.core import action +from pydantic import BaseModel + +class InputModel(BaseModel): + name: str + age: int + +class OutputModel(BaseModel): + greeting: str + +@action.pydantic(reads=[], writes=["greeting"]) +def greet(state: State, inputs: InputModel) -> OutputModel: + return OutputModel(greeting=f"Hello {inputs.name}, age {inputs.age}") +``` + +## Testing + +Actions are pure functions that are easy to test: + +```python +def test_my_action(): + state = State({"counter": 0}) + new_state = my_action(state, param="test") + assert new_state["counter"] == 1 + assert new_state["result"] == "test" +``` + +Test state machines by running them: + +```python +def test_application(): + app = build_my_app() + action, result, state = app.run(halt_after=["end"]) + assert state["final_value"] == expected_value +``` + +## Best Practices + +1. **Keep actions focused** - Single responsibility per action +2. **Declare reads/writes accurately** - Helps with debugging and optimization +3. **Use type hints** - Improves IDE support and catches bugs +4. **Enable tracking** - Always use `.with_tracker()` during development +5. **Test actions independently** - Actions are pure functions +6. **Use persistence for long-running workflows** - Enable state resumption +7. **Leverage hooks** - Add observability without changing core logic +8. **Visualize your state machine** - Use `.visualize()` to understand flow + +## Quick Links + +- Documentation: https://burr.apache.org/ +- GitHub: https://github.com/apache/burr +- Examples: `examples/` directory in the repository +- Discord: https://discord.gg/6Zy2DwP4f3 diff --git a/.claude/skills/burr/examples.md b/.claude/skills/burr/examples.md new file mode 100644 index 000000000..bfc731561 --- /dev/null +++ b/.claude/skills/burr/examples.md @@ -0,0 +1,629 @@ + + +# Apache Burr Code Examples + +Common patterns and working examples for building Burr applications. + +## Table of Contents +1. [Simple Counter](#simple-counter) +2. [Basic Chatbot](#basic-chatbot) +3. [Multi-Step Workflow](#multi-step-workflow) +4. [Conditional Branching](#conditional-branching) +5. [Looping with Conditions](#looping-with-conditions) +6. [Error Handling](#error-handling) +7. [Streaming Actions](#streaming-actions) +8. [Parallel Execution](#parallel-execution) +9. [State Persistence](#state-persistence) +10. [RAG Pattern](#rag-pattern) + +--- + +## Simple Counter + +Minimal example showing state updates and transitions. + +```python +from burr.core import action, State, ApplicationBuilder, default, expr + +@action(reads=["counter"], writes=["counter"]) +def increment(state: State) -> State: + return state.update(counter=state["counter"] + 1) + +@action(reads=["counter"], writes=["result"]) +def finish(state: State) -> State: + return state.update(result=f"Final count: {state['counter']}") + +app = ( + ApplicationBuilder() + .with_actions(increment, finish) + .with_transitions( + ("increment", "increment", expr("counter < 10")), + ("increment", "finish", default) + ) + .with_state(counter=0) + .with_entrypoint("increment") + .build() +) + +_, _, final_state = app.run(halt_after=["finish"]) +print(final_state["result"]) # "Final count: 10" +``` + +## Basic Chatbot + +Classic chatbot pattern with user input and AI response. + +```python +from burr.core import action, State, ApplicationBuilder, default + +@action(reads=[], writes=["chat_history", "prompt"]) +def human_input(state: State, prompt: str) -> State: + """Capture user input.""" + chat_item = {"role": "user", "content": prompt} + return ( + state.update(prompt=prompt) + .append(chat_history=chat_item) + ) + +@action(reads=["chat_history"], writes=["response", "chat_history"]) +def ai_response(state: State) -> State: + """Generate AI response.""" + # Call your LLM here + response = call_llm(state["chat_history"]) + chat_item = {"role": "assistant", "content": response} + return ( + state.update(response=response) + .append(chat_history=chat_item) + ) + +app = ( + ApplicationBuilder() + .with_actions(human_input, ai_response) + .with_transitions( + ("human_input", "ai_response"), + ("ai_response", "human_input") + ) + .with_state(chat_history=[]) + .with_entrypoint("human_input") + .with_tracker("local", project="chatbot") + .build() +) + +# Run one turn of conversation +_, _, state = app.run( + halt_after=["ai_response"], + inputs={"prompt": "Hello, how are you?"} +) +print(state["response"]) +``` + +## Multi-Step Workflow + +Chain multiple actions sequentially. + +```python +@action(reads=["raw_text"], writes=["cleaned_text"]) +def clean_text(state: State) -> State: + """Remove special characters and normalize.""" + cleaned = state["raw_text"].lower().strip() + return state.update(cleaned_text=cleaned) + +@action(reads=["cleaned_text"], writes=["tokens"]) +def tokenize(state: State) -> State: + """Split into tokens.""" + tokens = state["cleaned_text"].split() + return state.update(tokens=tokens) + +@action(reads=["tokens"], writes=["summary"]) +def summarize(state: State) -> State: + """Generate summary.""" + summary = f"Processed {len(state['tokens'])} tokens" + return state.update(summary=summary) + +app = ( + ApplicationBuilder() + .with_actions(clean_text, tokenize, summarize) + .with_transitions( + ("clean_text", "tokenize"), + ("tokenize", "summarize") + ) + .with_state(raw_text=" Hello World! ") + .with_entrypoint("clean_text") + .build() +) + +_, _, final_state = app.run(halt_after=["summarize"]) +``` + +## Conditional Branching + +Route execution based on state values. + +```python +from burr.core import when + +@action(reads=["user_type"], writes=["message"]) +def check_user_type(state: State, user_type: str) -> State: + return state.update(user_type=user_type) + +@action(reads=[], writes=["greeting"]) +def admin_greeting(state: State) -> State: + return state.update(greeting="Welcome, Administrator!") + +@action(reads=[], writes=["greeting"]) +def user_greeting(state: State) -> State: + return state.update(greeting="Welcome, User!") + +@action(reads=[], writes=["greeting"]) +def guest_greeting(state: State) -> State: + return state.update(greeting="Welcome, Guest!") + +app = ( + ApplicationBuilder() + .with_actions( + check_user_type, + admin_greeting, + user_greeting, + guest_greeting + ) + .with_transitions( + ("check_user_type", "admin_greeting", when(user_type="admin")), + ("check_user_type", "user_greeting", when(user_type="user")), + ("check_user_type", "guest_greeting", default) + ) + .with_entrypoint("check_user_type") + .build() +) + +_, _, state = app.run( + halt_after=["admin_greeting", "user_greeting", "guest_greeting"], + inputs={"user_type": "admin"} +) +``` + +## Looping with Conditions + +Implement loops using recursive transitions. + +```python +@action(reads=["items", "processed"], writes=["processed", "current_item"]) +def process_item(state: State) -> State: + """Process next item from list.""" + items = state["items"] + processed_count = state.get("processed", 0) + + current_item = items[processed_count] + # Process the item + result = transform(current_item) + + return state.update( + processed=processed_count + 1, + current_item=result + ) + +@action(reads=["processed"], writes=["done"]) +def finish_processing(state: State) -> State: + return state.update(done=True) + +app = ( + ApplicationBuilder() + .with_actions(process_item, finish_processing) + .with_transitions( + ("process_item", "process_item", expr("processed < len(items)")), + ("process_item", "finish_processing", default) + ) + .with_state(items=["a", "b", "c"], processed=0) + .with_entrypoint("process_item") + .build() +) +``` + +## Error Handling + +Handle errors gracefully by routing to error actions. + +```python +@action(reads=["data"], writes=["result", "error"]) +def risky_operation(state: State) -> State: + """Operation that might fail.""" + try: + result = dangerous_function(state["data"]) + return state.update(result=result, error=None) + except Exception as e: + return state.update(result=None, error=str(e)) + +@action(reads=["result"], writes=["success_message"]) +def handle_success(state: State) -> State: + return state.update(success_message=f"Success: {state['result']}") + +@action(reads=["error"], writes=["error_message"]) +def handle_error(state: State) -> State: + return state.update(error_message=f"Error: {state['error']}") + +@action(reads=["data"], writes=["result", "retry_count"]) +def retry_operation(state: State) -> State: + """Retry the operation.""" + retry_count = state.get("retry_count", 0) + 1 + try: + result = dangerous_function(state["data"]) + return state.update(result=result, error=None, retry_count=retry_count) + except Exception as e: + return state.update(result=None, error=str(e), retry_count=retry_count) + +app = ( + ApplicationBuilder() + .with_actions( + risky_operation, + handle_success, + handle_error, + retry_operation + ) + .with_transitions( + ("risky_operation", "handle_success", when(error=None)), + ("risky_operation", "retry_operation", + expr("error is not None and retry_count < 3")), + ("risky_operation", "handle_error", default), + ("retry_operation", "handle_success", when(error=None)), + ("retry_operation", "retry_operation", + expr("error is not None and retry_count < 3")), + ("retry_operation", "handle_error", default) + ) + .with_state(data="input", retry_count=0) + .with_entrypoint("risky_operation") + .build() +) +``` + +## Streaming Actions + +Stream intermediate results as they're generated. + +```python +from typing import Generator, Tuple + +@action(reads=["prompt"], writes=["response", "chunks"]) +def streaming_llm(state: State) -> Generator[State, None, Tuple[dict, State]]: + """Stream LLM response token by token.""" + chunks = [] + + # Stream tokens from LLM + for token in llm_stream(state["prompt"]): + chunks.append(token) + # Yield intermediate state + yield state.update( + chunks=chunks, + response="".join(chunks) + ) + + # Return final result + final_response = "".join(chunks) + result = {"response": final_response} + return result, state.update(**result) + +app = ( + ApplicationBuilder() + .with_actions(streaming_llm) + .with_state(prompt="Write a story") + .with_entrypoint("streaming_llm") + .build() +) + +# Stream results +for state in app.stream_result(halt_after=["streaming_llm"]): + print(state["response"], end="", flush=True) +``` + +## Parallel Execution + +Execute multiple actions in parallel. + +```python +from burr.core import graph + +@action(reads=["text"], writes=["sentiment"]) +def analyze_sentiment(state: State) -> State: + sentiment = get_sentiment(state["text"]) + return state.update(sentiment=sentiment) + +@action(reads=["text"], writes=["entities"]) +def extract_entities(state: State) -> State: + entities = extract_ner(state["text"]) + return state.update(entities=entities) + +@action(reads=["text"], writes=["keywords"]) +def extract_keywords(state: State) -> State: + keywords = get_keywords(state["text"]) + return state.update(keywords=keywords) + +@action(reads=["sentiment", "entities", "keywords"], writes=["analysis"]) +def combine_results(state: State) -> State: + """Combine all analysis results.""" + analysis = { + "sentiment": state["sentiment"], + "entities": state["entities"], + "keywords": state["keywords"] + } + return state.update(analysis=analysis) + +# Use graph builder for parallel execution +g = ( + graph.GraphBuilder() + .with_actions( + analyze_sentiment, + extract_entities, + extract_keywords, + combine_results + ) + .with_transitions( + # These three run in parallel + ("start", "analyze_sentiment"), + ("start", "extract_entities"), + ("start", "extract_keywords"), + # Wait for all three to complete + ( + ["analyze_sentiment", "extract_entities", "extract_keywords"], + "combine_results" + ) + ) + .build() +) + +app = ( + ApplicationBuilder() + .with_graph(g) + .with_state(text="Sample text to analyze") + .with_entrypoint("start") + .build() +) +``` + +## State Persistence + +Save and resume application state. + +```python +from burr.core.persistence import SQLLitePersister + +@action(reads=["step"], writes=["step", "result"]) +def long_running_step(state: State, step_name: str) -> State: + """Simulate a long-running operation.""" + result = expensive_computation(step_name) + return state.update( + step=step_name, + result=result + ) + +# Set up persister +persister = SQLLitePersister( + db_path="~/.burr/my_app.db", + table_name="app_state" +) +persister.initialize() + +app = ( + ApplicationBuilder() + .with_actions( + step1=long_running_step.bind(step_name="step1"), + step2=long_running_step.bind(step_name="step2"), + step3=long_running_step.bind(step_name="step3") + ) + .with_transitions( + ("step1", "step2"), + ("step2", "step3") + ) + .with_identifiers( + app_id="my-workflow-123", + partition_key="user-456" + ) + .with_state_persister(persister) + .initialize_from( + persister, + resume_at_next_action=True, # Resume from where it left off + default_state={"step": "none"}, + default_entrypoint="step1" + ) + .build() +) + +# Run - will resume from last saved state if it exists +app.run(halt_after=["step3"]) +``` + +## RAG Pattern + +Retrieval-Augmented Generation workflow. + +```python +@action(reads=[], writes=["query"]) +def process_query(state: State, user_query: str) -> State: + """Process and normalize user query.""" + return state.update(query=user_query) + +@action(reads=["query"], writes=["documents"]) +def retrieve_documents(state: State) -> State: + """Retrieve relevant documents from vector store.""" + docs = vector_db.search(state["query"], top_k=5) + return state.update(documents=docs) + +@action(reads=["documents"], writes=["reranked_documents"]) +def rerank_documents(state: State) -> State: + """Rerank documents for relevance.""" + reranked = reranker.rerank( + state["query"], + state["documents"] + ) + return state.update(reranked_documents=reranked) + +@action(reads=["query", "reranked_documents"], writes=["response"]) +def generate_response(state: State) -> State: + """Generate response using LLM with context.""" + context = "\n".join([doc.content for doc in state["reranked_documents"]]) + prompt = f"Context:\n{context}\n\nQuestion: {state['query']}\nAnswer:" + + response = llm.generate(prompt) + return state.update(response=response) + +@action(reads=["response"], writes=["formatted_response", "sources"]) +def format_response(state: State) -> State: + """Format response with citations.""" + sources = [ + {"title": doc.title, "url": doc.url} + for doc in state["reranked_documents"] + ] + formatted = { + "answer": state["response"], + "sources": sources + } + return state.update( + formatted_response=formatted, + sources=sources + ) + +app = ( + ApplicationBuilder() + .with_actions( + process_query, + retrieve_documents, + rerank_documents, + generate_response, + format_response + ) + .with_transitions( + ("process_query", "retrieve_documents"), + ("retrieve_documents", "rerank_documents"), + ("rerank_documents", "generate_response"), + ("generate_response", "format_response") + ) + .with_entrypoint("process_query") + .with_tracker("local", project="rag_chatbot") + .build() +) + +_, _, final_state = app.run( + halt_after=["format_response"], + inputs={"user_query": "What is Apache Burr?"} +) +print(final_state["formatted_response"]) +``` + +## Using Action Binding + +Reuse actions with different parameters. + +```python +@action(reads=["text"], writes=["processed_text"]) +def transform_text(state: State, operation: str, params: dict) -> State: + """Generic text transformation action.""" + text = state["text"] + + if operation == "uppercase": + result = text.upper() + elif operation == "replace": + result = text.replace(params["old"], params["new"]) + elif operation == "truncate": + result = text[:params["length"]] + + return state.update(processed_text=result) + +# Create specialized actions via binding +uppercase_action = transform_text.bind(operation="uppercase", params={}) +replace_action = transform_text.bind( + operation="replace", + params={"old": "bad", "new": "good"} +) +truncate_action = transform_text.bind( + operation="truncate", + params={"length": 100} +) + +app = ( + ApplicationBuilder() + .with_actions( + uppercase=uppercase_action, + replace=replace_action, + truncate=truncate_action + ) + .with_transitions( + ("uppercase", "replace"), + ("replace", "truncate") + ) + .with_state(text="This is bad text that is too long...") + .with_entrypoint("uppercase") + .build() +) +``` + +## Testing Actions + +Actions are pure functions - easy to test! + +```python +import pytest +from burr.core import State + +def test_increment_action(): + """Test the increment action.""" + state = State({"counter": 5}) + new_state = increment(state) + + assert new_state["counter"] == 6 + assert state["counter"] == 5 # Original unchanged (immutable) + +def test_chatbot_response(): + """Test AI response action.""" + state = State({ + "chat_history": [ + {"role": "user", "content": "Hello"} + ] + }) + + new_state = ai_response(state) + + assert "response" in new_state + assert len(new_state["chat_history"]) == 2 + assert new_state["chat_history"][-1]["role"] == "assistant" + +def test_conditional_flow(): + """Test complete application flow.""" + app = build_conditional_app() + + _, _, state = app.run( + halt_after=["admin_greeting"], + inputs={"user_type": "admin"} + ) + + assert state["greeting"] == "Welcome, Administrator!" + +@pytest.mark.asyncio +async def test_async_action(): + """Test async action.""" + state = State({"url": "https://api.example.com/data"}) + new_state = await fetch_data(state) + + assert "data" in new_state +``` + +--- + +For more examples, see the `examples/` directory in the Burr repository: +- `examples/hello-world-counter/` +- `examples/multi-modal-chatbot/` +- `examples/conversational-rag/` +- `examples/email-assistant/` diff --git a/.claude/skills/burr/patterns.md b/.claude/skills/burr/patterns.md new file mode 100644 index 000000000..cf32b3005 --- /dev/null +++ b/.claude/skills/burr/patterns.md @@ -0,0 +1,668 @@ + + +# Apache Burr Design Patterns & Best Practices + +Architectural guidance and best practices for building production-ready Burr applications. + +## Core Design Principles + +### 1. Single Responsibility Actions + +Each action should do one thing well. + +**❌ Bad - Action does too much:** +```python +@action(reads=["query"], writes=["response", "documents", "reranked", "formatted"]) +def do_everything(state: State) -> State: + # Retrieves, reranks, generates, and formats all in one + docs = retrieve(state["query"]) + reranked = rerank(docs) + response = generate(reranked) + formatted = format(response) + return state.update( + documents=docs, + reranked=reranked, + response=response, + formatted=formatted + ) +``` + +**✅ Good - Focused actions:** +```python +@action(reads=["query"], writes=["documents"]) +def retrieve_documents(state: State) -> State: + docs = retrieve(state["query"]) + return state.update(documents=docs) + +@action(reads=["documents"], writes=["reranked"]) +def rerank_documents(state: State) -> State: + reranked = rerank(state["documents"]) + return state.update(reranked=reranked) + +# ... separate actions for generate and format +``` + +**Benefits:** +- Easier to test +- Easier to debug (can see which action failed) +- Reusable components +- Clear visualization in the Burr UI + +### 2. Accurate reads/writes Declarations + +Declare exactly what each action reads and writes. + +**❌ Bad:** +```python +@action(reads=[], writes=[]) # Inaccurate! +def process_user(state: State) -> State: + user_id = state["user_id"] # Actually reads user_id + profile = fetch_profile(user_id) + return state.update(profile=profile) # Actually writes profile +``` + +**✅ Good:** +```python +@action(reads=["user_id"], writes=["profile"]) +def process_user(state: State) -> State: + user_id = state["user_id"] + profile = fetch_profile(user_id) + return state.update(profile=profile) +``` + +**Benefits:** +- Self-documenting code +- Better debugging in UI +- Enables future optimizations +- Catches errors early + +### 3. State Immutability + +Never mutate state directly - always use `.update()` or `.append()`. + +**❌ Bad:** +```python +@action(reads=["items"], writes=["items"]) +def add_item(state: State, item: str) -> State: + items = state["items"] + items.append(item) # Mutates state! + return state +``` + +**✅ Good:** +```python +@action(reads=["items"], writes=["items"]) +def add_item(state: State, item: str) -> State: + return state.append(items=item) +``` + +**Benefits:** +- Time-travel debugging +- State history for replay +- Prevents subtle bugs +- Enables state persistence + +### 4. Deterministic Actions + +Given the same state and inputs, an action should always produce the same output. + +**❌ Bad - Non-deterministic:** +```python +@action(reads=["data"], writes=["result"]) +def process_data(state: State) -> State: + # Random behavior makes debugging impossible + if random.random() > 0.5: + result = transform_a(state["data"]) + else: + result = transform_b(state["data"]) + return state.update(result=result) +``` + +**✅ Good - Deterministic:** +```python +@action(reads=["data", "strategy"], writes=["result"]) +def process_data(state: State) -> State: + # Behavior controlled by state + if state["strategy"] == "a": + result = transform_a(state["data"]) + else: + result = transform_b(state["data"]) + return state.update(result=result) +``` + +**Benefits:** +- Reproducible debugging +- Testable code +- Predictable behavior +- Easier to reason about + +## Common Patterns + +### Pattern: Request-Response Cycle + +For chatbots and conversational AI. + +```python +@action(reads=[], writes=["messages", "current_prompt"]) +def receive_message(state: State, prompt: str) -> State: + """Accept user input.""" + message = {"role": "user", "content": prompt} + return ( + state.append(messages=message) + .update(current_prompt=prompt) + ) + +@action(reads=["messages"], writes=["messages", "response"]) +def generate_response(state: State) -> State: + """Generate AI response.""" + response = llm_call(state["messages"]) + message = {"role": "assistant", "content": response} + return ( + state.append(messages=message) + .update(response=response) + ) + +@action(reads=["response"], writes=["display"]) +def format_output(state: State) -> State: + """Format for display.""" + return state.update(display=format_markdown(state["response"])) + +app = ( + ApplicationBuilder() + .with_actions(receive_message, generate_response, format_output) + .with_transitions( + ("receive_message", "generate_response"), + ("generate_response", "format_output"), + ("format_output", "receive_message") # Loop back for next message + ) + .with_state(messages=[]) + .with_entrypoint("receive_message") + .build() +) +``` + +### Pattern: Error Recovery with Retries + +Handle transient failures gracefully. + +```python +@action(reads=["url", "retry_count"], writes=["data", "error", "retry_count"]) +def fetch_with_retry(state: State) -> State: + """Fetch data with retry logic.""" + try: + data = http_get(state["url"]) + return state.update(data=data, error=None) + except Exception as e: + retry_count = state.get("retry_count", 0) + 1 + return state.update( + error=str(e), + retry_count=retry_count + ) + +@action(reads=["data"], writes=["processed"]) +def process_success(state: State) -> State: + """Process successful fetch.""" + return state.update(processed=transform(state["data"])) + +@action(reads=["error", "retry_count"], writes=["final_error"]) +def handle_failure(state: State) -> State: + """Handle permanent failure.""" + return state.update( + final_error=f"Failed after {state['retry_count']} retries: {state['error']}" + ) + +app = ( + ApplicationBuilder() + .with_actions(fetch_with_retry, process_success, handle_failure) + .with_transitions( + # Success path + ("fetch_with_retry", "process_success", when(error=None)), + # Retry path + ("fetch_with_retry", "fetch_with_retry", + expr("error is not None and retry_count < 3")), + # Failure path + ("fetch_with_retry", "handle_failure", default) + ) + .with_state(url="https://api.example.com", retry_count=0) + .with_entrypoint("fetch_with_retry") + .build() +) +``` + +### Pattern: Multi-Stage Pipeline + +Sequential data processing pipeline. + +```python +@action(reads=["raw_data"], writes=["validated_data"]) +def validate(state: State) -> State: + """Validate input data.""" + validated = validate_schema(state["raw_data"]) + return state.update(validated_data=validated) + +@action(reads=["validated_data"], writes=["transformed_data"]) +def transform(state: State) -> State: + """Transform data.""" + transformed = apply_transformations(state["validated_data"]) + return state.update(transformed_data=transformed) + +@action(reads=["transformed_data"], writes=["enriched_data"]) +def enrich(state: State) -> State: + """Enrich with external data.""" + enriched = add_external_data(state["transformed_data"]) + return state.update(enriched_data=enriched) + +@action(reads=["enriched_data"], writes=["result"]) +def finalize(state: State) -> State: + """Finalize output.""" + result = create_output(state["enriched_data"]) + return state.update(result=result) + +# Simple linear pipeline +app = ( + ApplicationBuilder() + .with_actions(validate, transform, enrich, finalize) + .with_transitions( + ("validate", "transform"), + ("transform", "enrich"), + ("enrich", "finalize") + ) + .with_entrypoint("validate") + .build() +) +``` + +### Pattern: Branching Decision Tree + +Route based on complex conditions. + +```python +@action(reads=["content"], writes=["analysis"]) +def analyze_content(state: State) -> State: + """Analyze content type and complexity.""" + analysis = { + "content_type": detect_type(state["content"]), + "complexity": calculate_complexity(state["content"]), + "language": detect_language(state["content"]) + } + return state.update(analysis=analysis) + +@action(reads=["content"], writes=["result"]) +def handle_simple_text(state: State) -> State: + return state.update(result=simple_processor(state["content"])) + +@action(reads=["content"], writes=["result"]) +def handle_complex_text(state: State) -> State: + return state.update(result=complex_processor(state["content"])) + +@action(reads=["content"], writes=["result"]) +def handle_code(state: State) -> State: + return state.update(result=code_processor(state["content"])) + +@action(reads=["content"], writes=["result"]) +def handle_unsupported(state: State) -> State: + return state.update(result={"error": "Unsupported content type"}) + +app = ( + ApplicationBuilder() + .with_actions( + analyze_content, + handle_simple_text, + handle_complex_text, + handle_code, + handle_unsupported + ) + .with_transitions( + ("analyze_content", "handle_simple_text", + expr("analysis['content_type'] == 'text' and analysis['complexity'] < 5")), + ("analyze_content", "handle_complex_text", + expr("analysis['content_type'] == 'text' and analysis['complexity'] >= 5")), + ("analyze_content", "handle_code", + when(analysis={"content_type": "code"})), + ("analyze_content", "handle_unsupported", default) + ) + .with_entrypoint("analyze_content") + .build() +) +``` + +### Pattern: Aggregating Parallel Results + +Run multiple analyses in parallel and combine. + +```python +from burr.core import graph + +@action(reads=["document"], writes=["summary"]) +async def summarize(state: State) -> State: + summary = await llm_summarize(state["document"]) + return state.update(summary=summary) + +@action(reads=["document"], writes=["sentiment"]) +async def analyze_sentiment(state: State) -> State: + sentiment = await get_sentiment(state["document"]) + return state.update(sentiment=sentiment) + +@action(reads=["document"], writes=["topics"]) +async def extract_topics(state: State) -> State: + topics = await get_topics(state["document"]) + return state.update(topics=topics) + +@action(reads=["summary", "sentiment", "topics"], writes=["report"]) +def create_report(state: State) -> State: + """Aggregate all analyses into final report.""" + report = { + "summary": state["summary"], + "sentiment": state["sentiment"], + "topics": state["topics"], + "timestamp": datetime.now() + } + return state.update(report=report) + +g = ( + graph.GraphBuilder() + .with_actions(summarize, analyze_sentiment, extract_topics, create_report) + .with_transitions( + # Parallel execution + ("start", "summarize"), + ("start", "analyze_sentiment"), + ("start", "extract_topics"), + # Wait for all, then aggregate + (["summarize", "analyze_sentiment", "extract_topics"], "create_report") + ) + .build() +) +``` + +### Pattern: State Machine with Memory + +Maintain conversation context and history. + +```python +@action(reads=["history"], writes=["history", "current_query"]) +def add_to_history(state: State, query: str) -> State: + """Add query to history with metadata.""" + history_item = { + "query": query, + "timestamp": datetime.now(), + "session_id": state.get("session_id") + } + return ( + state.append(history=history_item) + .update(current_query=query) + ) + +@action(reads=["history", "current_query"], writes=["response"]) +def generate_with_context(state: State) -> State: + """Generate response using conversation history.""" + # Build context from history + context = build_context_from_history(state["history"]) + + # Generate with full context + response = llm_call_with_context( + query=state["current_query"], + context=context + ) + return state.update(response=response) + +@action(reads=["history"], writes=["should_summarize"]) +def check_history_length(state: State) -> State: + """Check if history needs summarization.""" + should_summarize = len(state["history"]) > 10 + return state.update(should_summarize=should_summarize) + +@action(reads=["history"], writes=["history", "summary"]) +def summarize_history(state: State) -> State: + """Compress old history.""" + summary = create_summary(state["history"][:-5]) + recent = state["history"][-5:] + return state.update( + history=recent, + summary=summary + ) +``` + +## Best Practices + +### Testing Strategy + +**Unit test individual actions:** +```python +def test_action(): + state = State({"input": "test"}) + result = my_action(state) + assert result["output"] == "expected" +``` + +**Integration test the state machine:** +```python +def test_full_flow(): + app = build_app() + _, _, final_state = app.run(halt_after=["end"]) + assert final_state["result"] == expected_value +``` + +**Test with mock state:** +```python +def test_with_fixtures(): + state = State({ + "user": {"id": 123, "name": "Test"}, + "settings": {"mode": "test"} + }) + result = complex_action(state) + assert result["processed"] is True +``` + +### Observability + +**Always enable tracking during development:** +```python +app = ( + ApplicationBuilder() + .with_actions(...) + .with_tracker("local", project="my_app") + .build() +) +``` + +**Use the Burr UI to:** +- Visualize state machine execution +- Inspect state at each step +- Debug transition logic +- Profile action performance + +**Add custom hooks for production:** +```python +from burr.lifecycle import LifecycleAdapter + +class MetricsHook(LifecycleAdapter): + def post_run_step(self, action, result, state, **kwargs): + # Log metrics to your monitoring system + log_metric(f"action.{action.name}.duration", kwargs["duration"]) + log_metric(f"action.{action.name}.success", 1) +``` + +### State Management + +**Keep state flat when possible:** +```python +# ✅ Good +state = State({ + "user_id": 123, + "user_name": "Alice", + "user_email": "alice@example.com" +}) + +# ❌ Avoid deep nesting (harder to track) +state = State({ + "user": { + "profile": { + "personal": { + "name": "Alice" + } + } + } +}) +``` + +**Use meaningful key names:** +```python +# ✅ Good +state.update(validated_user_email="alice@example.com") + +# ❌ Bad +state.update(ve="alice@example.com") +``` + +### Performance Optimization + +**Use parallel execution for independent operations:** +```python +# Operations that don't depend on each other +("start", ["fetch_user", "fetch_products", "fetch_orders"]) +``` + +**Keep actions lightweight:** +```python +# ❌ Bad - Heavy computation in action +@action(reads=["data"], writes=["result"]) +def process(state: State) -> State: + # Hours of computation + result = train_ml_model(state["data"]) + return state.update(result=result) + +# ✅ Better - Break into steps with state persistence +@action(reads=["data"], writes=["preprocessed"]) +def preprocess(state: State) -> State: + return state.update(preprocessed=preprocess_data(state["data"])) + +@action(reads=["preprocessed"], writes=["checkpoint"]) +def train_epoch(state: State) -> State: + # Train one epoch, save checkpoint + checkpoint = train_one_epoch(state["preprocessed"]) + return state.update(checkpoint=checkpoint) +``` + +### Production Deployment + +**Enable persistence for long-running workflows:** +```python +from burr.core.persistence import SQLLitePersister + +persister = SQLLitePersister("prod.db", "workflows") +app = ( + ApplicationBuilder() + .with_actions(...) + .with_state_persister(persister) + .initialize_from(persister, resume_at_next_action=True) + .build() +) +``` + +**Use unique identifiers:** +```python +app = ( + ApplicationBuilder() + .with_identifiers( + app_id=f"workflow-{workflow_id}", + partition_key=f"user-{user_id}" + ) + .build() +) +``` + +**Add error boundaries:** +```python +@action(reads=["data"], writes=["result", "error"]) +def safe_operation(state: State) -> State: + try: + result = risky_operation(state["data"]) + return state.update(result=result, error=None) + except Exception as e: + logger.error(f"Operation failed: {e}") + return state.update(result=None, error=str(e)) +``` + +## Anti-Patterns to Avoid + +### ❌ Shared Mutable State + +```python +# Don't do this! +cache = {} + +@action(reads=["key"], writes=["value"]) +def get_cached(state: State) -> State: + # Mutates external state - not reproducible! + if state["key"] not in cache: + cache[state["key"]] = expensive_call() + return state.update(value=cache[state["key"]]) +``` + +### ❌ Side Effects Without State Tracking + +```python +# Don't do this! +@action(reads=["data"], writes=["saved"]) +def save_to_db(state: State) -> State: + # Side effect not tracked in state + db.save(state["data"]) + return state.update(saved=True) + +# Better: Track what was saved +@action(reads=["data"], writes=["saved", "saved_id"]) +def save_to_db(state: State) -> State: + saved_id = db.save(state["data"]) + return state.update(saved=True, saved_id=saved_id) +``` + +### ❌ God Actions + +```python +# Don't do this! +@action(reads=["everything"], writes=["everything"]) +def do_all_the_things(state: State) -> State: + # 500 lines of code doing multiple things + pass +``` + +### ❌ Missing Error Handling + +```python +# Don't do this! +@action(reads=["url"], writes=["data"]) +def fetch(state: State) -> State: + # No error handling - will crash the app + data = requests.get(state["url"]).json() + return state.update(data=data) +``` + +## Summary + +- **Keep actions small and focused** +- **Declare reads/writes accurately** +- **Never mutate state** +- **Make actions deterministic** +- **Use tracking and visualization** +- **Test actions independently** +- **Enable persistence for long workflows** +- **Handle errors gracefully** +- **Leverage parallel execution** +- **Monitor with hooks in production** diff --git a/.claude/skills/burr/plugin.json b/.claude/skills/burr/plugin.json new file mode 100644 index 000000000..5414da390 --- /dev/null +++ b/.claude/skills/burr/plugin.json @@ -0,0 +1,34 @@ +{ + "name": "burr", + "version": "1.0.0", + "description": "Apache Burr development assistant - expert guidance for building stateful applications with state machines", + "author": "Apache Burr Contributors", + "license": "Apache-2.0", + "repository": "https://github.com/apache/burr", + "homepage": "https://burr.apache.org", + "keywords": [ + "burr", + "state-machine", + "llm", + "agent", + "workflow", + "stateful", + "python" + ], + "skill": { + "name": "burr", + "description": "Helps developers build stateful applications using Apache Burr, including state machines, actions, transitions, and observability", + "files": [ + "SKILL.md", + "api-reference.md", + "examples.md", + "patterns.md", + "troubleshooting.md", + "README.md" + ] + }, + "install": { + "message": "Apache Burr skill installed! Use /burr to get expert help building state machine applications.", + "requirements": [] + } +} diff --git a/.claude/skills/burr/troubleshooting.md b/.claude/skills/burr/troubleshooting.md new file mode 100644 index 000000000..da5111250 --- /dev/null +++ b/.claude/skills/burr/troubleshooting.md @@ -0,0 +1,732 @@ + + +# Apache Burr Troubleshooting Guide + +Common issues and solutions when working with Burr. + +## Installation Issues + +### Issue: `burr` command not found after installation + +**Problem:** +```bash +$ burr +command not found: burr +``` + +**Solutions:** + +1. Install with UI dependencies: +```bash +pip install "burr[start]" +``` + +2. Check if burr is in your PATH: +```bash +which burr +# or +python -m burr +``` + +3. If using poetry: +```bash +poetry add "burr[start]" +poetry run burr +``` + +### Issue: UI won't start or shows errors + +**Problem:** +``` +Error starting Burr UI: Module not found +``` + +**Solutions:** + +1. Ensure you installed the `[start]` extra: +```bash +pip install "burr[start]" +``` + +2. Check port is not already in use: +```bash +# Default is port 7241 +lsof -i :7241 +``` + +3. Specify custom port: +```bash +burr --port 8000 +``` + +4. Check storage directory permissions: +```bash +ls -la ~/.burr +``` + +## State Machine Issues + +### Issue: Infinite loops in transitions + +**Problem:** +```python +# State machine never halts +app.run(halt_after=["end"]) # Never reaches "end" +``` + +**Common Causes:** + +1. **Missing halt condition:** +```python +# ❌ Bad - loops forever +.with_transitions( + ("process", "process", default) # Always loops! +) + +# ✅ Good - has exit condition +.with_transitions( + ("process", "process", expr("counter < 10")), + ("process", "end", default) +) +``` + +2. **Condition never becomes true:** +```python +# ❌ Bad - condition may never be met +.with_transitions( + ("wait", "wait", when(status="pending")), + ("wait", "done", when(status="complete")) + # What if status is "error"? Stuck forever! +) + +# ✅ Good - always has fallback +.with_transitions( + ("wait", "wait", when(status="pending")), + ("wait", "done", when(status="complete")), + ("wait", "error_handler", default) +) +``` + +**Debugging:** +1. Use `.visualize()` to see the graph: +```python +app.visualize(output_file_path="debug.png", include_conditions=True) +``` + +2. Add logging in actions: +```python +@action(reads=["counter"], writes=["counter"]) +def process(state: State) -> State: + print(f"Counter: {state['counter']}") # Debug output + return state.update(counter=state["counter"] + 1) +``` + +3. Use the Burr UI to watch execution in real-time: +```bash +burr +``` + +### Issue: Wrong action executes + +**Problem:** +``` +Expected action 'process_data' but 'error_handler' executed instead +``` + +**Common Causes:** + +1. **Transition condition order matters:** +```python +# ❌ Bad - default matches first +.with_transitions( + ("check", "error", default), # This always matches! + ("check", "success", when(valid=True)) +) + +# ✅ Good - specific conditions first +.with_transitions( + ("check", "success", when(valid=True)), + ("check", "error", default) +) +``` + +2. **State value is not what you expect:** +```python +# Debug: Check actual state values +@action(reads=["value"], writes=["result"]) +def check_value(state: State) -> State: + print(f"Value is: {state['value']}, type: {type(state['value'])}") + # Maybe it's a string "True" not boolean True? + return state.update(result=state["value"]) +``` + +3. **Using `when()` with complex objects:** +```python +# ❌ Bad - object comparison may not work as expected +.with_transitions( + ("check", "next", when(user={"id": 123})) # Dict comparison +) + +# ✅ Good - use simpler state values +.with_transitions( + ("check", "next", when(user_id=123)) # Direct value comparison +) +``` + +## State Issues + +### Issue: State not updating + +**Problem:** +```python +# State remains unchanged after action +state_before = app.state["counter"] # 0 +app.run(halt_after=["increment"]) +state_after = app.state["counter"] # Still 0! +``` + +**Common Causes:** + +1. **Not returning updated state:** +```python +# ❌ Bad - returns None +@action(reads=["counter"], writes=["counter"]) +def increment(state: State) -> State: + state.update(counter=state["counter"] + 1) + # Missing return! + +# ✅ Good - returns updated state +@action(reads=["counter"], writes=["counter"]) +def increment(state: State) -> State: + return state.update(counter=state["counter"] + 1) +``` + +2. **Mutating state directly:** +```python +# ❌ Bad - mutates state (doesn't work) +@action(reads=["items"], writes=["items"]) +def add_item(state: State, item: str) -> State: + state["items"].append(item) # This doesn't work! + return state + +# ✅ Good - uses .append() method +@action(reads=["items"], writes=["items"]) +def add_item(state: State, item: str) -> State: + return state.append(items=item) +``` + +3. **Typo in state key:** +```python +# ❌ Bad - creates new key instead of updating existing +@action(reads=["counter"], writes=["counter"]) +def increment(state: State) -> State: + return state.update(couter=state["counter"] + 1) # Typo! + +# ✅ Good - correct key name +@action(reads=["counter"], writes=["counter"]) +def increment(state: State) -> State: + return state.update(counter=state["counter"] + 1) +``` + +### Issue: KeyError accessing state + +**Problem:** +```python +KeyError: 'missing_key' +``` + +**Solutions:** + +1. **Use `.get()` with default:** +```python +# ❌ Risky - may not exist +value = state["key"] + +# ✅ Safe - provides default +value = state.get("key", default_value) +``` + +2. **Check if key exists:** +```python +if "key" in state: + value = state["key"] +else: + value = default +``` + +3. **Initialize state properly:** +```python +app = ( + ApplicationBuilder() + .with_state( + counter=0, # Initialize all keys + items=[], + status="pending" + ) + .build() +) +``` + +4. **Check reads declaration:** +```python +# ❌ Bad - tries to read undeclared key +@action(reads=[], writes=["result"]) +def process(state: State) -> State: + value = state["input"] # Not in reads! + return state.update(result=value) + +# ✅ Good - declares what it reads +@action(reads=["input"], writes=["result"]) +def process(state: State) -> State: + value = state["input"] + return state.update(result=value) +``` + +## Action Issues + +### Issue: Action inputs not working + +**Problem:** +```python +app.run(halt_after=["process"], inputs={"param": "value"}) +# Error: unexpected keyword argument 'param' +``` + +**Solution:** + +Add parameter to action function: +```python +# ❌ Bad - no parameter for input +@action(reads=[], writes=["result"]) +def process(state: State) -> State: + # How do I access the input? + pass + +# ✅ Good - accepts input parameter +@action(reads=[], writes=["result"]) +def process(state: State, param: str) -> State: + return state.update(result=param) +``` + +### Issue: Streaming action not working + +**Problem:** +```python +# No intermediate results appear +for state in app.stream_result(halt_after=["end"]): + print(state) # Only prints final state +``` + +**Solution:** + +Use generator pattern with yields: +```python +# ❌ Bad - regular action (no streaming) +@action(reads=["input"], writes=["output"]) +def process(state: State) -> State: + result = slow_operation() + return state.update(output=result) + +# ✅ Good - streaming action +@action(reads=["input"], writes=["output"]) +def process(state: State) -> Generator[State, None, Tuple[dict, State]]: + for chunk in slow_operation(): + # Yield intermediate states + yield state.update(current_chunk=chunk) + + # Return final result + result = {"output": "done"} + return result, state.update(**result) +``` + +### Issue: Async action errors + +**Problem:** +``` +RuntimeError: Event loop is closed +``` + +**Solutions:** + +1. **Use `arun()` for async applications:** +```python +# ❌ Bad - using sync run with async actions +app.run(halt_after=["async_action"]) + +# ✅ Good - using async run +await app.arun(halt_after=["async_action"]) +``` + +2. **Make sure action is marked async:** +```python +# ✅ Async action +@action(reads=["url"], writes=["data"]) +async def fetch_data(state: State) -> State: + async with httpx.AsyncClient() as client: + response = await client.get(state["url"]) + return state.update(data=response.json()) +``` + +3. **Mix sync and async carefully:** +```python +# You can have both sync and async actions in the same app +# Burr handles this automatically +app = ( + ApplicationBuilder() + .with_actions( + sync_action, # Regular action + async_action # Async action + ) + .build() +) + +# Use arun() if any action is async +await app.arun(halt_after=["async_action"]) +``` + +## Persistence Issues + +### Issue: State not persisting + +**Problem:** +```python +# State is lost between runs +app = build_app() +app.run(halt_after=["step1"]) +# Restart app +app = build_app() # Starts from beginning, not step1 +``` + +**Solution:** + +Set up persistence properly: +```python +from burr.core.persistence import SQLLitePersister + +persister = SQLLitePersister("app.db", "state") +persister.initialize() # Don't forget to initialize! + +app = ( + ApplicationBuilder() + .with_actions(...) + .with_identifiers( + app_id="my-workflow", # Required for persistence + partition_key="user-123" # Required for persistence + ) + .with_state_persister(persister) + .initialize_from( + persister, + resume_at_next_action=True, + default_state={"step": 0}, + default_entrypoint="start" + ) + .build() +) +``` + +### Issue: Multiple app instances conflict + +**Problem:** +```python +# Both apps save to same location and conflict +app1 = build_app() +app2 = build_app() +``` + +**Solution:** + +Use unique identifiers: +```python +app1 = ( + ApplicationBuilder() + .with_identifiers( + app_id="workflow-1", + partition_key="user-alice" + ) + .build() +) + +app2 = ( + ApplicationBuilder() + .with_identifiers( + app_id="workflow-2", + partition_key="user-bob" + ) + .build() +) +``` + +## Tracking / UI Issues + +### Issue: Application not appearing in UI + +**Problem:** +``` +Burr UI is running but my application doesn't show up +``` + +**Solutions:** + +1. **Make sure tracking is enabled:** +```python +app = ( + ApplicationBuilder() + .with_tracker("local", project="my_project") + .build() +) +``` + +2. **Check storage directory:** +```python +# Specify storage directory +app = ( + ApplicationBuilder() + .with_tracker( + "local", + project="my_project", + params={"storage_dir": "~/.burr"} + ) + .build() +) + +# Then launch UI with same directory +# burr --storage-dir ~/.burr +``` + +3. **Run the application:** +```python +# Tracking data is only created when app runs +app.run(halt_after=["some_action"]) +``` + +4. **Check UI is pointing to correct directory:** +```bash +burr --storage-dir ~/.burr +``` + +### Issue: Visualization not generating + +**Problem:** +```python +app.visualize(output_file_path="graph.png") +# No file created, or error about graphviz +``` + +**Solutions:** + +1. **Install graphviz:** +```bash +# macOS +brew install graphviz + +# Ubuntu +sudo apt-get install graphviz + +# Then install Python package +pip install graphviz +``` + +2. **Check file path:** +```python +# Use absolute path +app.visualize( + output_file_path="/full/path/to/graph.png", + format="png" +) +``` + +3. **Try different formats:** +```python +# Try PDF if PNG doesn't work +app.visualize( + output_file_path="graph.pdf", + format="pdf" +) +``` + +## Performance Issues + +### Issue: Application runs slowly + +**Problem:** +``` +Application takes too long to execute +``` + +**Solutions:** + +1. **Use parallel execution:** +```python +# Run independent actions in parallel +from burr.core import graph + +g = ( + graph.GraphBuilder() + .with_actions(action1, action2, action3) + .with_transitions( + # These run in parallel + ("start", ["action1", "action2"]), + (["action1", "action2"], "action3") + ) + .build() +) +``` + +2. **Profile actions:** +```python +import time + +@action(reads=["input"], writes=["output"]) +def slow_action(state: State) -> State: + start = time.time() + result = expensive_operation(state["input"]) + print(f"Action took {time.time() - start:.2f}s") + return state.update(output=result) +``` + +3. **Check for unnecessary state copies:** +```python +# ❌ Slow - repeated state updates +new_state = state +for item in items: + new_state = new_state.update(item=process(item)) + +# ✅ Faster - batch update +processed_items = [process(item) for item in items] +new_state = state.update(items=processed_items) +``` + +4. **Use async for I/O-bound operations:** +```python +# ❌ Slow - sequential I/O +def fetch_all(state: State) -> State: + data1 = requests.get(url1).json() + data2 = requests.get(url2).json() + return state.update(data1=data1, data2=data2) + +# ✅ Fast - parallel async I/O +async def fetch_all(state: State) -> State: + async with httpx.AsyncClient() as client: + data1, data2 = await asyncio.gather( + client.get(url1), + client.get(url2) + ) + return state.update(data1=data1.json(), data2=data2.json()) +``` + +## Testing Issues + +### Issue: Tests fail with tracking enabled + +**Problem:** +```python +# Tests create tracking data and clutter filesystem +``` + +**Solution:** + +Disable tracking in tests: +```python +def build_app(enable_tracking: bool = True): + builder = ApplicationBuilder().with_actions(...) + + if enable_tracking: + builder = builder.with_tracker("local", project="my_app") + + return builder.build() + +# In tests +def test_my_app(): + app = build_app(enable_tracking=False) + # Test without creating tracking data +``` + +Or use temporary directory: +```python +import tempfile + +def test_with_tracking(): + with tempfile.TemporaryDirectory() as tmpdir: + app = ( + ApplicationBuilder() + .with_tracker( + "local", + project="test", + params={"storage_dir": tmpdir} + ) + .build() + ) + # Test runs, tracking data cleaned up automatically +``` + +## Type Checking Issues + +### Issue: Type errors with State + +**Problem:** +```python +# Type checker complains about state access +def my_function(state: State): + value: int = state["key"] # Type checker error +``` + +**Solution:** + +State values are typed as `Any` by default. Use runtime checks or type assertions: +```python +def my_function(state: State): + value = state["key"] + assert isinstance(value, int) + # Now type checker knows it's int +``` + +Or use Pydantic integration for type safety: +```python +from burr.core import action +from pydantic import BaseModel + +class MyState(BaseModel): + key: int + +@action.pydantic(reads=["key"], writes=["result"]) +def my_action(state: State, inputs: MyState) -> dict: + # inputs.key is typed as int + return {"result": inputs.key + 1} +``` + +## Getting Help + +If you're still stuck: + +1. **Check the documentation:** https://burr.apache.org/ +2. **Search GitHub issues:** https://github.com/apache/burr/issues +3. **Ask on Discord:** https://discord.gg/6Zy2DwP4f3 +4. **Enable debug logging:** +```python +import logging +logging.basicConfig(level=logging.DEBUG) +``` + +When asking for help, include: +- Burr version: `pip show burr` +- Python version: `python --version` +- Minimal code example that reproduces the issue +- Full error message and traceback +- State machine visualization if relevant: `app.visualize()` diff --git a/.rat-excludes b/.rat-excludes index 7a51824eb..b6ebab132 100644 --- a/.rat-excludes +++ b/.rat-excludes @@ -12,6 +12,9 @@ # Jupyter notebooks (JSON format, cannot practically add headers) .*\.ipynb +# Plugin manifests (JSON format, cannot practically add headers) +.*plugin\.json + # Data files (CSV - not source code) .*\.csv diff --git a/CLAUDE_SKILL.md b/CLAUDE_SKILL.md new file mode 100644 index 000000000..650c3da4a --- /dev/null +++ b/CLAUDE_SKILL.md @@ -0,0 +1,168 @@ + + +# Apache Burr Claude Code Skill + +This repository includes a Claude Code skill that makes Claude an expert assistant for building Apache Burr applications. + +## Quick Install + +### Option 1: Install from GitHub (Easiest) + +```bash +# Install to personal skills directory +claude skill install https://github.com/apache/burr/.claude/skills/burr + +# Or install to project +cd your-project +claude skill install https://github.com/apache/burr/.claude/skills/burr --project +``` + +### Option 2: Manual Install + +```bash +# Clone the repository +git clone https://github.com/apache/burr + +# Install to personal skills directory +cp -r burr/.claude/skills/burr ~/.claude/skills/ + +# Or install to your project +cp -r burr/.claude/skills/burr .claude/skills/ +``` + +## What You Get + +Once installed, Claude becomes an expert in: + +- **Building Burr applications** - Get help scaffolding state machines from scratch +- **Writing actions** - Create properly structured action functions with correct decorators +- **Defining transitions** - Set up conditional logic and state machine flows +- **Adding observability** - Configure the Burr UI and tracking +- **Debugging issues** - Troubleshoot common problems with state machines +- **Following best practices** - Learn recommended patterns and anti-patterns +- **Code review** - Get feedback on your Burr code + +## Usage + +### Automatic Activation + +Just mention Burr in your conversation: + +``` +"Help me build a chatbot with Burr" +"Why isn't my state updating?" +"Show me how to add retry logic" +``` + +### Manual Invocation + +Use the `/burr` command explicitly: + +``` +/burr How do I create a streaming action? +/burr Review this code for best practices +/burr Show me an example of parallel execution +``` + +## What's Included + +The skill contains comprehensive documentation: + +- **SKILL.md** - Main instructions for Claude +- **api-reference.md** - Complete Burr API documentation +- **examples.md** - Working code examples for common patterns +- **patterns.md** - Best practices and design patterns +- **troubleshooting.md** - Solutions to common issues +- **README.md** - Installation and usage guide + +## Example Interactions + +**Building a new application:** +``` +You: "Help me create a Burr application for document processing" +Claude: I'll help you create a multi-stage pipeline... +[Generates complete application with actions and transitions] +``` + +**Getting examples:** +``` +You: "Show me how to implement retry logic in Burr" +Claude: Here's a retry pattern with error recovery... +[Provides working code example] +``` + +**Debugging:** +``` +You: "My state machine is looping infinitely" +Claude: Let me help you debug the transitions... +[Analyzes and suggests fixes] +``` + +**Code review:** +``` +You: "Review this Burr code for best practices" +Claude: Let me check your application... +[Provides detailed feedback] +``` + +## Documentation + +Full documentation available at: +- **Online**: https://burr.apache.org/getting_started/claude-skill +- **Local**: `docs/getting_started/claude-skill.rst` in this repository + +## Requirements + +- Claude Code CLI installed +- No Burr installation required (Claude can help you install when needed) + +## Customization + +You can customize the skill for your team: + +1. Edit the files in `.claude/skills/burr/` +2. Add your own examples to `examples.md` +3. Update patterns in `patterns.md` +4. Extend the API reference + +## Contributing + +Found a bug or want to improve the skill? + +- **Report issues**: https://github.com/apache/burr/issues +- **Submit fixes**: Open a pull request with your improvements +- **Suggest examples**: Share useful patterns you've discovered + +We welcome contributions of all sizes - from typo fixes to new examples! + +## Related Resources + +- **Burr Documentation**: https://burr.apache.org +- **GitHub Repository**: https://github.com/apache/burr +- **Example Applications**: `examples/` directory +- **Discord Community**: https://discord.gg/6Zy2DwP4f3 + +## License + +Apache License 2.0 - See LICENSE file in the root of the repository. + +--- + +Built with ❤️ by the Apache Burr community. diff --git a/README.md b/README.md index c65c035ed..bc86c7ec8 100644 --- a/README.md +++ b/README.md @@ -144,6 +144,22 @@ including tooling to build a UI in streamlit and watch your state machine execut See the documentation for [getting started](https://burr.apache.org/getting_started/simple-example), and follow the example. Then read through some of the concepts and write your own application! +### Using Claude Code? + +If you use [Claude Code](https://claude.com/claude-code), install the [Burr Claude skill](.claude/skills/burr/) to get expert assistance building Burr applications. The skill teaches Claude about Burr's APIs, best practices, and common patterns. + +```bash +# Easy install from GitHub +claude skill install https://github.com/apache/burr/.claude/skills/burr + +# Or manual install +cp -r /path/to/burr/.claude/skills/burr ~/.claude/skills/ +``` + +Then just ask Claude naturally: *"Help me build a Burr application"* or use `/burr` for specific help. + +See [CLAUDE_SKILL.md](CLAUDE_SKILL.md) for installation details and the [skill documentation](https://burr.apache.org/getting_started/claude-skill) for full usage guide. + ## 📃 Comparison against common frameworks While Apache Burr is attempting something (somewhat) unique, there are a variety of tools that occupy similar spaces: diff --git a/docs/getting_started/claude-skill.rst b/docs/getting_started/claude-skill.rst new file mode 100644 index 000000000..02de37619 --- /dev/null +++ b/docs/getting_started/claude-skill.rst @@ -0,0 +1,439 @@ +.. + Licensed to the Apache Software Foundation (ASF) under one + or more contributor license agreements. See the NOTICE file + distributed with this work for additional information + regarding copyright ownership. The ASF licenses this file + to you under the Apache License, Version 2.0 (the + "License"); you may not use this file except in compliance + with the License. You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, + software distributed under the License is distributed on an + "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + KIND, either express or implied. See the License for the + specific language governing permissions and limitations + under the License. + + +================== +Claude Code Skill +================== + +Apache Burr includes a comprehensive Claude Code skill that makes Claude an expert in helping you build Burr applications. + +What is the Claude Code Skill? +=============================== + +The Burr Claude skill is a plugin for `Claude Code `_, Anthropic's official CLI tool. When active, it teaches Claude how to: + +* Build new Burr applications from scratch +* Write properly structured actions and transitions +* Follow best practices and design patterns +* Debug common issues +* Provide working code examples +* Review your code for correctness + +Installation +============ + +Option 1: Install from GitHub (Easiest) +---------------------------------------- + +Use the Claude CLI to install directly from GitHub: + +.. code-block:: bash + + # Install to personal skills directory + claude skill install https://github.com/apache/burr/.claude/skills/burr + + # Or install to your current project + claude skill install https://github.com/apache/burr/.claude/skills/burr --project + +Option 2: Manual Personal Installation +--------------------------------------- + +Copy the skill to your personal Claude skills directory: + +.. code-block:: bash + + # Clone the Burr repository + git clone https://github.com/apache/burr + + # Copy skill to personal directory + cp -r burr/.claude/skills/burr ~/.claude/skills/ + +Option 3: Manual Project Installation +-------------------------------------- + +For team projects, copy the skill to your project's ``.claude/skills/`` directory: + +.. code-block:: bash + + # From your project root + cp -r /path/to/burr/.claude/skills/burr .claude/skills/ + +Verify Installation +------------------- + +Check that the skill is available: + +.. code-block:: bash + + # In Claude Code, try: + /burr --help + +Or ask Claude naturally: + + "Help me build a Burr application" + +Usage +===== + +Manual Invocation +----------------- + +Use the ``/burr`` command to explicitly invoke the skill: + +.. code-block:: text + + /burr How do I create a streaming action? + + /burr Review this action for best practices + + /burr Show me an example of parallel execution + +Automatic Invocation +-------------------- + +Claude automatically loads the skill when it detects you're working with Burr: + +.. code-block:: text + + "I'm building a chatbot with Burr" + + "Why isn't my action updating the state?" + + "Show me how to add persistence" + +What Can It Do? +=============== + +Code Generation +--------------- + +Ask Claude to generate complete Burr applications: + +**Example:** + +.. code-block:: text + + "Create a Burr application for a RAG chatbot with document retrieval and reranking" + +Claude will generate: + +* Action functions with proper ``@action`` decorators +* State machine transitions with conditions +* Tracking configuration +* Complete application setup + +Code Review +----------- + +Get feedback on your Burr code: + +**Example:** + +.. code-block:: text + + "Review this application for best practices" + +Claude will check: + +* Correct ``reads`` and ``writes`` declarations +* State immutability +* Transition coverage +* Error handling +* Performance considerations + +Learning & Examples +------------------- + +Get working examples for common patterns: + +**Example:** + +.. code-block:: text + + "Show me how to implement retry logic" + +Claude provides: + +* Complete working code +* Explanation of the pattern +* Best practices +* References to documentation + +Debugging Help +-------------- + +Troubleshoot issues with Claude's help: + +**Example:** + +.. code-block:: text + + "My state machine is looping infinitely" + +Claude will: + +* Analyze transition logic +* Suggest using ``.visualize()`` +* Provide solutions +* Reference troubleshooting docs + +Skill Contents +============== + +The skill includes comprehensive documentation: + +API Reference +------------- + +Complete documentation of Burr's API: + +* Actions and decorators +* State management +* ApplicationBuilder +* Transitions and conditions +* Persistence +* Tracking and hooks + +Examples +-------- + +Working code examples for: + +* Basic chatbots +* Streaming actions +* Parallel execution +* Error handling and retries +* RAG patterns +* State persistence +* Testing + +Design Patterns +--------------- + +Best practices and architectural guidance: + +* Single responsibility actions +* State immutability +* Deterministic actions +* Error recovery patterns +* Multi-stage pipelines +* Branching decision trees + +Troubleshooting +--------------- + +Solutions for common issues: + +* Installation problems +* State machine loops +* State not updating +* Persistence issues +* Performance optimization + +Common Use Cases +================ + +Building a Chatbot +------------------ + +.. code-block:: text + + "Help me build a multi-modal chatbot with Burr" + +Claude will create a complete chatbot with: + +* User input action +* LLM response action +* State management for chat history +* Transitions for conversation flow + +Adding Features +--------------- + +.. code-block:: text + + "Add streaming responses to my chatbot" + +Claude will: + +* Show how to convert to a streaming action +* Provide the generator pattern +* Update the application setup + +Debugging +--------- + +.. code-block:: text + + "My action isn't updating state, what's wrong?" + +Claude will: + +* Review your code +* Identify the issue (likely missing ``return``) +* Provide the fix +* Explain why it matters + +Tips for Best Results +====================== + +1. **Be specific** - "Help me add retry logic to my fetch action" is better than "help with errors" + +2. **Show your code** - Claude works best when it can see what you're building + +3. **Ask for examples** - "Show me an example of..." gets working code + +4. **Reference the skill's docs** - Ask Claude to check the API reference or patterns guide + +5. **Use visualization** - Ask Claude to suggest using ``app.visualize()`` when debugging + +Example Conversation +==================== + +Here's a typical interaction: + +.. code-block:: text + + You: I want to build a Burr application that processes documents through multiple stages + + Claude: I'll help you create a multi-stage document processing pipeline with Burr. + Let me create actions for each stage... + + [Claude generates code with actions for validation, transformation, enrichment, and output] + + You: How do I add error handling? + + Claude: I'll show you how to add error recovery with retries. Here's the pattern... + + [Claude adds error handling actions and transitions] + + You: Can you review this code? + + Claude: Let me check your application for best practices... + + [Claude reviews and provides feedback] + +Integration with Development +============================= + +The skill integrates seamlessly with your development workflow: + +* **During design** - Get help planning your state machine architecture +* **While coding** - Generate boilerplate and follow patterns +* **When debugging** - Troubleshoot issues and understand errors +* **In code review** - Verify best practices are followed + +Customizing the Skill +====================== + +You can customize the skill for your needs: + +1. Edit ``SKILL.md`` to change instructions +2. Add your own examples to ``examples.md`` +3. Update ``patterns.md`` with team-specific practices +4. Extend ``api-reference.md`` with custom actions + +Example customization: + +.. code-block:: bash + + cd ~/.claude/skills/burr + # Edit the skill files + vim examples.md + +Updating the Skill +================== + +To get the latest version: + +.. code-block:: bash + + cd /path/to/burr + git pull + cp -r .claude/skills/burr ~/.claude/skills/ + +Related Resources +================= + +* `Claude Code Documentation `_ +* `Burr Examples `_ +* `Burr Discord `_ + +FAQ +=== + +**Do I need Burr installed to use the skill?** + +No, but Claude can help you install it when needed. + +**Can I use this with other frameworks?** + +Yes! Burr integrates well with LangChain, LlamaIndex, Apache Hamilton, and others. + +**Will this work with older Burr versions?** + +The skill is designed for current Burr versions. Some APIs may differ in older releases. + +**How do I disable the skill?** + +Rename the skill directory: + +.. code-block:: bash + + mv ~/.claude/skills/burr ~/.claude/skills/burr.disabled + +**Can I share my customizations?** + +Yes! Contribute improvements back to the project via pull request. + +Contributing +============ + +Found an issue or want to improve the skill? We welcome contributions! + +Reporting Issues +---------------- + +If you find a bug or have a suggestion: + +1. Check existing issues at https://github.com/apache/burr/issues +2. Open a new issue with: + + * Clear description of the problem or suggestion + * Steps to reproduce (for bugs) + * Expected vs actual behavior + * Burr version and environment details + +Contributing Improvements +------------------------- + +We especially appreciate pull requests! To contribute: + +1. Fork the repository +2. Edit the skill files in ``.claude/skills/burr/`` +3. Test your changes with Claude Code +4. Submit a PR to https://github.com/apache/burr with: + + * Clear description of what you changed and why + * Examples showing the improvement + * Any relevant issue references + +Small fixes like typos, improved examples, or clearer explanations are always welcome! + +The Burr community appreciates all contributions, big and small. diff --git a/docs/getting_started/index.rst b/docs/getting_started/index.rst index bd5d29c73..5594161da 100644 --- a/docs/getting_started/index.rst +++ b/docs/getting_started/index.rst @@ -31,4 +31,5 @@ The following section of the docs will walk you through Burr and how to integrat why-burr install simple-example + claude-skill up-next From 1d644f358c2caaabc89b111294e30b3bfbdf07e0 Mon Sep 17 00:00:00 2001 From: Stefan Krawczyk Date: Sat, 31 Jan 2026 11:43:19 -0800 Subject: [PATCH 2/7] Fix Claude Code skill documentation for Apache Burr This commit corrects and improves the Claude Code skill documentation with three major fixes: 1. Fix parallelism documentation - Remove incorrect graph-based parallelism examples - Add correct MapStates, MapActions, MapActionsAndStates patterns - Include RunnableGraph for subgraph parallelism - Update examples in SKILL.md, examples.md, patterns.md, troubleshooting.md 2. Fix state management guidance - Correct action return pattern: Tuple[dict, State] - Show proper state access with bracket notation: state["key"] - Document state immutability and method chaining - Add comprehensive Pydantic typed state section - Contrast regular state vs Pydantic typed state patterns - Update all examples to show correct patterns 3. Use proper noun "Apache Burr" consistently - Update all skill files to use "Apache Burr" instead of just "Burr" - Maintain consistency across documentation Additional improvements: - Delete redundant CLAUDE_SKILL.md (consolidate with .claude/skills/burr/README.md) - Update README.md to point to correct skill location - Add complete state method documentation (update, append, extend, increment, etc.) - Add Pydantic typed state examples to examples.md --- .claude/skills/burr/README.md | 46 ++-- .claude/skills/burr/SKILL.md | 177 ++++++++++++-- .claude/skills/burr/api-reference.md | 305 +++++++++++++++++++++++-- .claude/skills/burr/examples.md | 201 +++++++++++----- .claude/skills/burr/patterns.md | 88 ++++--- .claude/skills/burr/troubleshooting.md | 60 +++-- CLAUDE_SKILL.md | 168 -------------- README.md | 2 +- 8 files changed, 718 insertions(+), 329 deletions(-) delete mode 100644 CLAUDE_SKILL.md diff --git a/.claude/skills/burr/README.md b/.claude/skills/burr/README.md index 2b79fbd78..a4a47c46b 100644 --- a/.claude/skills/burr/README.md +++ b/.claude/skills/burr/README.md @@ -23,7 +23,7 @@ A comprehensive Claude Code skill for building stateful applications with Apache ## What is this? -This is a Claude Code skill that teaches Claude how to help you build applications using Apache Burr. When active, Claude becomes an expert in Burr's APIs, best practices, and common patterns. +This is a Claude Code skill that teaches Claude how to help you build applications using Apache Burr. When active, Claude becomes an expert in Apache Burr's APIs, best practices, and common patterns. ## Installation @@ -80,13 +80,13 @@ Or just ask Claude naturally: This skill helps you: -- **Build new Burr applications** - Get help scaffolding state machines +- **Build new Apache Burr applications** - Get help scaffolding state machines - **Write actions** - Create properly structured action functions - **Define transitions** - Set up conditional and default transitions -- **Add observability** - Configure tracking and the Burr UI +- **Add observability** - Configure tracking and the Apache Burr UI - **Debug issues** - Troubleshoot common problems - **Follow best practices** - Learn recommended patterns -- **Review code** - Get feedback on your Burr applications +- **Review code** - Get feedback on your Apache Burr applications ## Usage Examples @@ -104,14 +104,14 @@ Explicitly invoke the skill with the `/burr` command: ### Automatic Invocation -Claude will automatically load the skill when it detects you're working with Burr: +Claude will automatically load the skill when it detects you're working with Apache Burr: ``` -"I'm building a chatbot with Burr and need help with the state machine" +"I'm building a chatbot with Apache Burr and need help with the state machine" "Why isn't my action updating the state?" -"Show me an example of parallel execution in Burr" +"Show me an example of parallel execution in Apache Burr" ``` ## What's Included @@ -131,7 +131,7 @@ Claude will reference these files to provide accurate, helpful guidance. ### Code Generation ``` -"Create a Burr application that processes user queries with RAG" +"Create an Apache Burr application that processes user queries with RAG" ``` Claude will generate a complete application with actions, transitions, and tracking. @@ -139,7 +139,7 @@ Claude will generate a complete application with actions, transitions, and track ### Code Review ``` -"Review my Burr application for best practices" +"Review my Apache Burr application for best practices" ``` Claude will check for: @@ -158,12 +158,12 @@ Claude will check for: Claude will: - Analyze your transitions - Suggest using `.visualize()` to see the graph -- Recommend fixes based on common issues +- Recommend fixes based on common issues with Apache Burr ### Learning & Examples ``` -"Show me how to implement retries in Burr" +"Show me how to implement retries in Apache Burr" ``` Claude will provide working examples from the examples.md reference. @@ -201,8 +201,8 @@ The skill activates when you: ## Examples of What to Ask ### Getting Started -- "Help me create my first Burr application" -- "What's the basic structure of a Burr action?" +- "Help me create my first Apache Burr application" +- "What's the basic structure of an Apache Burr action?" - "Show me a simple chatbot example" ### Building Features @@ -216,7 +216,7 @@ The skill activates when you: - "How do I debug an infinite loop?" ### Best Practices -- "Review this code for Burr best practices" +- "Review this code for Apache Burr best practices" - "Is this the right way to structure my state machine?" - "How should I handle errors in actions?" @@ -230,9 +230,9 @@ git pull cp -r .claude/skills/burr ~/.claude/skills/ ``` -## Integration with Burr Project +## Integration with Apache Burr Project -If you're working in the Burr repository itself, the skill is already available at `.claude/skills/burr/`. +If you're working in the Apache Burr repository itself, the skill is already available at `.claude/skills/burr/`. ## Contributing @@ -265,14 +265,14 @@ Small fixes like typos, improved examples, or clearer explanations are always we ## Related Resources -- **Burr Documentation**: https://burr.apache.org/ +- **Apache Burr Documentation**: https://burr.apache.org/ - **GitHub**: https://github.com/apache/burr -- **Examples**: See `examples/` directory in the Burr repository +- **Examples**: See `examples/` directory in the Apache Burr repository - **Discord**: https://discord.gg/6Zy2DwP4f3 ## FAQ -**Q: Do I need Burr installed to use this skill?** +**Q: Do I need Apache Burr installed to use this skill?** A: No, but Claude can help you install it: `pip install "burr[start]"` @@ -280,13 +280,13 @@ A: No, but Claude can help you install it: `pip install "burr[start]"` A: Yes! Edit the files in `.claude/skills/burr/` to customize behavior, add your own examples, or modify the API reference. -**Q: Will this work with older versions of Burr?** +**Q: Will this work with older versions of Apache Burr?** -A: This skill is designed for current Burr versions. Some APIs may differ in older versions. +A: This skill is designed for current Apache Burr versions. Some APIs may differ in older versions. **Q: Can I use this skill with other frameworks?** -A: Yes! Burr integrates well with LangChain, LlamaIndex, Apache Hamilton, and other frameworks. The skill includes integration guidance. +A: Yes! Apache Burr integrates well with LangChain, LlamaIndex, Apache Hamilton, and other frameworks. The skill includes integration guidance. **Q: How do I disable the skill temporarily?** @@ -303,6 +303,6 @@ See the [LICENSE](../../../LICENSE) file in the root of the repository. --- -Built with ❤️ by the Burr community. +Built with ❤️ by the Apache Burr community. For help, join our [Discord](https://discord.gg/6Zy2DwP4f3) or open an issue on [GitHub](https://github.com/apache/burr/issues). diff --git a/.claude/skills/burr/SKILL.md b/.claude/skills/burr/SKILL.md index ba10c1c09..044d16843 100644 --- a/.claude/skills/burr/SKILL.md +++ b/.claude/skills/burr/SKILL.md @@ -26,7 +26,7 @@ allowed-tools: Read, Grep, Glob, Bash(python *, burr, pip *) # Apache Burr Development Assistant -You are an expert in Apache Burr (incubating), a Python framework for building stateful applications using state machines. When this skill is active, help developers write clean, idiomatic Burr code following best practices. +You are an expert in Apache Burr (incubating), a Python framework for building stateful applications using state machines. When this skill is active, help developers write clean, idiomatic Apache Burr code following best practices. ## Core Expertise @@ -51,7 +51,7 @@ Refer to these supporting files for detailed information: ### 1. Building New Applications -When users want to create a Burr application: +When users want to create an Apache Burr application: 1. **Start with actions** - Define `@action` decorated functions 2. **Use ApplicationBuilder** - Follow the builder pattern @@ -62,12 +62,20 @@ When users want to create a Burr application: Example skeleton: ```python from burr.core import action, State, ApplicationBuilder, default +from typing import Tuple @action(reads=["input_key"], writes=["output_key"]) -def my_action(state: State) -> State: - # Your logic here - result = process(state["input_key"]) - return state.update(output_key=result) +def my_action(state: State) -> Tuple[dict, State]: + # 1. Read from state using bracket notation + input_value = state["input_key"] + + # 2. Your logic here + output_value = process(input_value) + + # 3. Return tuple: (result_dict, new_state) + # - result_dict: exposed to callers and tracking + # - new_state: returned by state.update() (creates new State object) + return {"output_key": output_value}, state.update(output_key=output_value) app = ( ApplicationBuilder() @@ -79,10 +87,11 @@ app = ( .build() ) -result = app.run(halt_after=["next_action"]) +# Run returns (action, result_dict, final_state) +action, result, state = app.run(halt_after=["next_action"]) ``` -### 2. Reviewing Burr Code +### 2. Reviewing Apache Burr Code When reviewing code: - ✅ Check that actions declare correct `reads` and `writes` @@ -95,7 +104,7 @@ When reviewing code: ### 3. Explaining Concepts -When explaining Burr features: +When explaining Apache Burr features: - Use concrete examples from [examples.md](examples.md) - Reference the appropriate section in [api-reference.md](api-reference.md) - Show both simple and complex variations @@ -135,23 +144,147 @@ async def async_action(state: State) -> State: **Parallel execution**: ```python -from burr.core import graph - -graph = ( - graph.GraphBuilder() - .with_actions(action1, action2, action3) - .with_transitions( - ("start", "action1"), - ("start", "action2"), # These run in parallel - (["action1", "action2"], "action3") +from burr.core.parallelism import MapStates, RunnableGraph + +# Apply same action to multiple states +class TestMultiplePrompts(MapStates): + def action(self, state: State, inputs: dict) -> Action | Callable | RunnableGraph: + return query_llm.with_name("query_llm") + + def states(self, state: State, context: ApplicationContext, inputs: dict): + for prompt in state["prompts"]: + yield state.update(prompt=prompt) + + def reduce(self, state: State, states): + results = [s["result"] for s in states] + return state.update(all_results=results) + + @property + def reads(self) -> list[str]: + return ["prompts"] + + @property + def writes(self) -> list[str]: + return ["all_results"] + +app = ApplicationBuilder().with_actions( + multi_prompt=TestMultiplePrompts() +).build() +``` + +## State Management Patterns + +### Regular State (Dictionary-Based) + +**Reading from state:** +```python +# Use bracket notation to access state values +value = state["key"] +chat_history = state["chat_history"] +counter = state["counter"] +``` + +**Updating state:** +State is immutable. Methods return NEW State objects: +```python +# state.update() - set/update keys, returns new State +new_state = state.update(counter=5, name="Alice") + +# state.append() - append to lists, returns new State +new_state = state.append(chat_history={"role": "user", "content": "hi"}) + +# state.increment() - increment numbers, returns new State +new_state = state.increment(counter=1) + +# Chaining - each method returns a State, enabling fluent patterns +new_state = state.update(prompt=prompt).append(chat_history=item) +``` + +**Action return pattern:** +Actions return `Tuple[dict, State]`: +```python +from typing import Tuple + +@action(reads=["prompt"], writes=["response", "chat_history"]) +def ai_respond(state: State) -> Tuple[dict, State]: + # 1. Read from state + prompt = state["prompt"] + + # 2. Process + response = call_llm(prompt) + + # 3. Return (result_dict, new_state) + # result_dict is exposed to callers/tracking + # new_state is the updated immutable state + return {"response": response}, state.update(response=response).append( + chat_history={"role": "assistant", "content": response} ) +``` + +**Shorthand (also valid):** +```python +@action(reads=["counter"], writes=["counter"]) +def increment(state: State) -> State: + result = {"counter": state["counter"] + 1} + # Framework infers result from state updates + return state.update(**result) +``` + +### Pydantic Typed State (Different Pattern) + +**Define state model:** +```python +from pydantic import BaseModel, Field +from typing import Optional + +class ApplicationState(BaseModel): + prompt: Optional[str] = Field(default=None, description="User prompt") + response: Optional[str] = Field(default=None, description="AI response") + chat_history: list[dict] = Field(default_factory=list) +``` + +**Configure application:** +```python +from burr.integrations.pydantic import PydanticTypingSystem + +app = ( + ApplicationBuilder() + .with_typing(PydanticTypingSystem(ApplicationState)) + .with_state(ApplicationState()) .build() ) ``` +**Access typed state:** +```python +# Use attribute access (not bracket notation) +@action.pydantic(reads=["prompt"], writes=["response"]) +def ai_respond(state: ApplicationState) -> ApplicationState: + # 1. Read using attributes + prompt = state.prompt + + # 2. Process + response = call_llm(prompt) + + # 3. Mutate in-place and return state + # (Mutation happens on internal copy) + state.response = response + return state +``` + +**Key differences:** + +| Aspect | Regular State | Pydantic Typed State | +|--------|---------------|---------------------| +| **Access** | `state["key"]` | `state.key` | +| **Return** | `Tuple[dict, State]` | `ApplicationState` | +| **Decorator** | `@action(reads=[], writes=[])` | `@action.pydantic(reads=[], writes=[])` | +| **Updates** | Must use `.update()`, `.append()` | In-place mutation | +| **Type Safety** | Runtime only | IDE support + validation | + ## Code Quality Standards -When writing or reviewing Burr code: +When writing or reviewing Apache Burr code: 1. **Type annotations**: Always use type hints for state and action parameters 2. **Action purity**: Actions should be deterministic given the same state @@ -172,7 +305,7 @@ When writing or reviewing Burr code: ## Integration Scenarios -Burr works well with: +Apache Burr works well with: - **LLM frameworks**: OpenAI, Anthropic, Langchain, LlamaIndex - **Apache Hamilton**: For DAG execution within actions - **Streaming**: Streamlit, FastAPI, gradio for UI @@ -190,7 +323,7 @@ Burr works well with: 1. **State machines make complex logic simple** - Encourage users to think in terms of states and transitions 2. **Observability is built-in** - Always recommend using the tracking UI -3. **Framework agnostic** - Burr doesn't dictate how to build models or query APIs +3. **Framework agnostic** - Apache Burr doesn't dictate how to build models or query APIs 4. **Testability first** - Actions are pure functions that are easy to test 5. **Production ready** - Persistence, hooks, and tracking enable production deployment @@ -209,4 +342,4 @@ Burr works well with: - Look at tests in `tests/` for usage patterns - Reference official documentation at https://burr.apache.org/ -Remember: Burr is about making stateful applications easy to build, understand, and debug. Focus on clear state machines and leverage the built-in observability tools. +Remember: Apache Burr is about making stateful applications easy to build, understand, and debug. Focus on clear state machines and leverage the built-in observability tools. diff --git a/.claude/skills/burr/api-reference.md b/.claude/skills/burr/api-reference.md index 8ade6924f..58bc17aa0 100644 --- a/.claude/skills/burr/api-reference.md +++ b/.claude/skills/burr/api-reference.md @@ -19,7 +19,7 @@ under the License. # Apache Burr API Reference -This is a quick reference for the most commonly used Burr APIs. For complete documentation, see https://burr.apache.org/ +This is a quick reference for the most commonly used Apache Burr APIs. For complete documentation, see https://burr.apache.org/ ## Core Imports @@ -134,28 +134,72 @@ if "key" in state: ### Updating State -State is immutable. Use these methods to create updated copies: +State is **immutable**. All methods return NEW State objects: ```python # Update single or multiple keys new_state = state.update(counter=5, name="Alice") -# Append to a list +# Append to a list (creates list if doesn't exist) new_state = state.append(messages={"role": "user", "content": "hello"}) +# Increment numbers +new_state = state.increment(counter=1) + +# Extend lists with multiple items +new_state = state.extend(tags=["tag1", "tag2", "tag3"]) + # Wipe state (keep only specified keys) new_state = state.wipe(keep=["counter"]) +# Chain operations (each returns State) +new_state = state.update(prompt=prompt).append(history=item).increment(count=1) + # Update with dictionary new_state = state.update(**{"key": "value"}) ``` +### Using State in Actions + +Actions return `Tuple[dict, State]`: + +```python +from typing import Tuple + +@action(reads=["input"], writes=["output"]) +def my_action(state: State) -> Tuple[dict, State]: + # 1. Read from state + input_value = state["input"] + + # 2. Process + output_value = process(input_value) + + # 3. Return (result_dict, new_state) + return {"output": output_value}, state.update(output=output_value) +``` + +The result dict is exposed to callers and tracking systems. The new state flows to the next action. + +**Shorthand (also valid):** +```python +@action(reads=["counter"], writes=["counter"]) +def increment(state: State) -> State: + result = {"counter": state["counter"] + 1} + return state.update(**result) # Framework infers result +``` + ### State Methods -- `.update(**kwargs) -> State`: Update one or more keys -- `.append(**kwargs) -> State`: Append to list values +- `.update(**kwargs) -> State`: Set/update one or more keys +- `.append(**kwargs) -> State`: Append to list values (creates list if needed) +- `.extend(**kwargs) -> State`: Extend lists with multiple items +- `.increment(**kwargs) -> State`: Increment integer values - `.wipe(keep: List[str] = None, delete: List[str] = None) -> State`: Remove keys +- `.merge(other: State) -> State`: Merge two states (other wins on conflicts) +- `.subset(*keys: str) -> State`: Return new State with only specified keys - `.get(key, default=None) -> Any`: Get value with default +- `.get_all() -> dict`: Get all state as dictionary +- `.serialize() -> dict`: Serialize state to JSON-compatible dict - `.subset(*keys) -> State`: Create new state with only specified keys ## ApplicationBuilder @@ -291,8 +335,10 @@ Transition from multiple sources to multiple targets: # Multiple sources to one target (["action1", "action2"], "action3") -# One source to multiple targets (for parallelism) +# One source to multiple targets (conditional branching - only one executes) ("start", ["parallel1", "parallel2"]) + +# For actual parallelism, use MapActions/MapStates/MapActionsAndStates ``` ## Application @@ -349,6 +395,179 @@ app.visualize( ) ``` +## Parallelism + +Apache Burr provides high-level APIs for parallel execution of actions or subgraphs. + +### MapStates + +Apply the same action to multiple state variations. + +```python +from burr.core.parallelism import MapStates +from burr.core import action, State, ApplicationContext +from typing import Dict, Any + +@action(reads=["prompt"], writes=["result"]) +def query_llm(state: State) -> State: + result = call_llm(state["prompt"]) + return state.update(result=result) + +class TestMultiplePrompts(MapStates): + def action(self, state: State, inputs: Dict[str, Any]): + return query_llm.with_name("query_llm") + + def states(self, state: State, context: ApplicationContext, inputs: Dict[str, Any]): + for prompt in state["prompts"]: + yield state.update(prompt=prompt) + + def reduce(self, state: State, states): + results = [s["result"] for s in states] + return state.update(all_results=results) + + @property + def reads(self) -> list[str]: + return ["prompts"] + + @property + def writes(self) -> list[str]: + return ["all_results"] +``` + +### MapActions + +Apply different actions to the same state. + +```python +from burr.core.parallelism import MapActions + +class TestMultipleLLMs(MapActions): + def actions(self, state: State, context: ApplicationContext, inputs: Dict[str, Any]): + yield query_gpt4.with_name("gpt4") + yield query_claude.with_name("claude") + yield query_o1.with_name("o1") + + def state(self, state: State, inputs: Dict[str, Any]) -> State: + return state # Pass same state to all actions + + def reduce(self, state: State, states): + results = [s["result"] for s in states] + return state.update(all_results=results) + + @property + def reads(self) -> list[str]: + return ["prompt"] + + @property + def writes(self) -> list[str]: + return ["all_results"] +``` + +### MapActionsAndStates + +Run all combinations of actions and states (cartesian product). + +```python +from burr.core.parallelism import MapActionsAndStates + +class TestModelsAndPrompts(MapActionsAndStates): + def actions(self, state: State, context: ApplicationContext, inputs: Dict[str, Any]): + for model in ["gpt-4", "claude", "o1"]: + yield query_llm.bind(model=model).with_name(f"query_{model}") + + def states(self, state: State, context: ApplicationContext, inputs: Dict[str, Any]): + for prompt in state["prompts"]: + yield state.update(prompt=prompt) + + def reduce(self, state: State, states): + results = [] + for s in states: + results.append({"model": s["model"], "prompt": s["prompt"], "result": s["result"]}) + return state.update(all_results=results) + + @property + def reads(self) -> list[str]: + return ["prompts"] + + @property + def writes(self) -> list[str]: + return ["all_results"] +``` + +### RunnableGraph + +Wrap a graph for use as a subgraph in parallel execution. + +```python +from burr.core.parallelism import RunnableGraph +from burr.core.graph import GraphBuilder + +graph = ( + GraphBuilder() + .with_actions(action1, action2, action3) + .with_transitions( + ("action1", "action2"), + ("action2", "action3") + ) + .build() +) + +runnable = RunnableGraph( + graph=graph, + entrypoint="action1", + halt_after=["action3"] +) + +# Use in MapStates or MapActions +class RunSubgraphs(MapStates): + def action(self, state: State, inputs: Dict[str, Any]): + return runnable # Return the RunnableGraph + + def states(self, state: State, context: ApplicationContext, inputs: Dict[str, Any]): + for item in state["items"]: + yield state.update(current_item=item) + + def reduce(self, state: State, states): + results = [s["final_result"] for s in states] + return state.update(all_results=results) + + @property + def reads(self) -> list[str]: + return ["items"] + + @property + def writes(self) -> list[str]: + return ["all_results"] +``` + +### Executors + +Control how parallel tasks are executed. + +```python +from concurrent.futures import ThreadPoolExecutor + +# Use multithreading (default) +app = ( + ApplicationBuilder() + .with_parallel_executor(ThreadPoolExecutor(max_workers=10)) + .with_actions(parallel_action=MyParallelAction()) + .build() +) + +# For Ray-based distributed execution +from burr.integrations.ray import RayExecutor +import ray + +ray.init() +app = ( + ApplicationBuilder() + .with_parallel_executor(RayExecutor()) + .with_actions(parallel_action=MyParallelAction()) + .build() +) +``` + ## Persistence ### Built-in Persisters @@ -507,26 +726,72 @@ app = ( app.run(halt_after=["process"], inputs={"user_prompt": "Hello"}) ``` -## Type Safety +## Type Safety with Pydantic + +### Pydantic Typed State + +Use Pydantic models for type-safe state with IDE support, autocomplete, and validation. + +**Define state model:** +```python +from pydantic import BaseModel, Field +from typing import Optional + +class ApplicationState(BaseModel): + prompt: Optional[str] = Field(default=None, description="User prompt") + response: Optional[str] = Field(default=None, description="AI response") + count: int = Field(default=0, description="Request count") +``` + +**Configure application:** +```python +from burr.integrations.pydantic import PydanticTypingSystem -### Pydantic Integration +app = ( + ApplicationBuilder() + .with_typing(PydanticTypingSystem(ApplicationState)) + .with_state(ApplicationState()) + .build() +) +``` +**Use in actions:** ```python -from burr.core import action -from pydantic import BaseModel +@action.pydantic(reads=["prompt"], writes=["response"]) +def process(state: ApplicationState, llm_client) -> ApplicationState: + # Access state as attributes (not brackets) + user_prompt = state.prompt + + # Process + response = llm_client.generate(user_prompt) -class InputModel(BaseModel): - name: str - age: int + # Mutate in-place and return (mutation on internal copy) + state.response = response + return state +``` -class OutputModel(BaseModel): - greeting: str +**Access typed state after run:** +```python +action, result, state = app.run(halt_after=["process"]) -@action.pydantic(reads=[], writes=["greeting"]) -def greet(state: State, inputs: InputModel) -> OutputModel: - return OutputModel(greeting=f"Hello {inputs.name}, age {inputs.age}") +# Use .data property to access Pydantic model +print(state.data.response) # IDE autocomplete works! +print(state.data.count) ``` +### Key Differences: Regular vs Pydantic State + +| Feature | Regular State | Pydantic Typed State | +|---------|---------------|---------------------| +| **Definition** | `State({"key": value})` | Pydantic `BaseModel` | +| **Access** | `state["key"]` | `state.key` | +| **Updates** | `state.update()`, `state.append()` | In-place mutation | +| **Return Type** | `Tuple[dict, State]` | `ApplicationState` | +| **Decorator** | `@action(...)` | `@action.pydantic(...)` | +| **Type Safety** | Runtime only | Compile-time + Runtime | +| **IDE Support** | No autocomplete | Full autocomplete | +| **Validation** | Manual | Automatic via Pydantic | + ## Testing Actions are pure functions that are easy to test: @@ -561,7 +826,7 @@ def test_application(): ## Quick Links -- Documentation: https://burr.apache.org/ +- Apache Burr Documentation: https://burr.apache.org/ - GitHub: https://github.com/apache/burr -- Examples: `examples/` directory in the repository +- Examples: `examples/` directory in the Apache Burr repository - Discord: https://discord.gg/6Zy2DwP4f3 diff --git a/.claude/skills/burr/examples.md b/.claude/skills/burr/examples.md index bfc731561..25b77ffd3 100644 --- a/.claude/skills/burr/examples.md +++ b/.claude/skills/burr/examples.md @@ -19,7 +19,7 @@ under the License. # Apache Burr Code Examples -Common patterns and working examples for building Burr applications. +Common patterns and working examples for building Apache Burr applications. ## Table of Contents 1. [Simple Counter](#simple-counter) @@ -32,6 +32,9 @@ Common patterns and working examples for building Burr applications. 8. [Parallel Execution](#parallel-execution) 9. [State Persistence](#state-persistence) 10. [RAG Pattern](#rag-pattern) +11. [Using Action Binding](#using-action-binding) +12. [Testing Actions](#testing-actions) +13. [Pydantic Typed State](#pydantic-typed-state) --- @@ -44,11 +47,16 @@ from burr.core import action, State, ApplicationBuilder, default, expr @action(reads=["counter"], writes=["counter"]) def increment(state: State) -> State: - return state.update(counter=state["counter"] + 1) + # Read from state using bracket notation + result = {"counter": state["counter"] + 1} + # State methods return new State objects + return state.update(**result) @action(reads=["counter"], writes=["result"]) def finish(state: State) -> State: - return state.update(result=f"Final count: {state['counter']}") + # Access state values with state["key"] + result = {"result": f"Final count: {state['counter']}"} + return state.update(**result) app = ( ApplicationBuilder() @@ -62,7 +70,8 @@ app = ( .build() ) -_, _, final_state = app.run(halt_after=["finish"]) +# run() returns (action, result_dict, final_state) +action, result, final_state = app.run(halt_after=["finish"]) print(final_state["result"]) # "Final count: 10" ``` @@ -72,25 +81,31 @@ Classic chatbot pattern with user input and AI response. ```python from burr.core import action, State, ApplicationBuilder, default +from typing import Tuple @action(reads=[], writes=["chat_history", "prompt"]) -def human_input(state: State, prompt: str) -> State: +def human_input(state: State, prompt: str) -> Tuple[dict, State]: """Capture user input.""" + # Build chat item chat_item = {"role": "user", "content": prompt} - return ( - state.update(prompt=prompt) - .append(chat_history=chat_item) - ) + + # Return (result_dict, new_state) + # Chain state updates: update() returns State, then append() returns new State + return {"prompt": prompt}, state.update(prompt=prompt).append(chat_history=chat_item) @action(reads=["chat_history"], writes=["response", "chat_history"]) -def ai_response(state: State) -> State: +def ai_response(state: State) -> Tuple[dict, State]: """Generate AI response.""" - # Call your LLM here - response = call_llm(state["chat_history"]) + # Read from state using bracket notation + chat_history = state["chat_history"] + + # Call your LLM + response = call_llm(chat_history) chat_item = {"role": "assistant", "content": response} - return ( - state.update(response=response) - .append(chat_history=chat_item) + + # Return result and chained state updates + return {"response": response}, state.update(response=response).append( + chat_history=chat_item ) app = ( @@ -107,11 +122,13 @@ app = ( ) # Run one turn of conversation -_, _, state = app.run( +# run() returns (action, result_dict, final_state) +action, result, state = app.run( halt_after=["ai_response"], inputs={"prompt": "Hello, how are you?"} ) -print(state["response"]) +print(result["response"]) # Access result from result dict +print(state["chat_history"]) # Access state using bracket notation ``` ## Multi-Step Workflow @@ -332,10 +349,12 @@ for state in app.stream_result(halt_after=["streaming_llm"]): ## Parallel Execution -Execute multiple actions in parallel. +Execute multiple actions in parallel using Apache Burr's parallelism APIs. ```python -from burr.core import graph +from burr.core.parallelism import MapActions, RunnableGraph +from burr.core import action, State, ApplicationContext +from typing import Dict, Any @action(reads=["text"], writes=["sentiment"]) def analyze_sentiment(state: State) -> State: @@ -352,44 +371,42 @@ def extract_keywords(state: State) -> State: keywords = get_keywords(state["text"]) return state.update(keywords=keywords) -@action(reads=["sentiment", "entities", "keywords"], writes=["analysis"]) -def combine_results(state: State) -> State: - """Combine all analysis results.""" - analysis = { - "sentiment": state["sentiment"], - "entities": state["entities"], - "keywords": state["keywords"] - } - return state.update(analysis=analysis) - -# Use graph builder for parallel execution -g = ( - graph.GraphBuilder() - .with_actions( - analyze_sentiment, - extract_entities, - extract_keywords, - combine_results - ) - .with_transitions( - # These three run in parallel - ("start", "analyze_sentiment"), - ("start", "extract_entities"), - ("start", "extract_keywords"), - # Wait for all three to complete - ( - ["analyze_sentiment", "extract_entities", "extract_keywords"], - "combine_results" - ) - ) - .build() -) +# Run multiple actions in parallel on the same state +class ParallelTextAnalysis(MapActions): + def actions(self, state: State, context: ApplicationContext, inputs: Dict[str, Any]): + yield analyze_sentiment.with_name("sentiment_analysis") + yield extract_entities.with_name("entity_extraction") + yield extract_keywords.with_name("keyword_extraction") + + def state(self, state: State, inputs: Dict[str, Any]) -> State: + return state # Pass state as-is to all actions + + def reduce(self, state: State, states) -> State: + """Combine all analysis results.""" + analysis = {} + for sub_state in states: + if "sentiment" in sub_state: + analysis["sentiment"] = sub_state["sentiment"] + if "entities" in sub_state: + analysis["entities"] = sub_state["entities"] + if "keywords" in sub_state: + analysis["keywords"] = sub_state["keywords"] + return state.update(analysis=analysis) + + @property + def reads(self) -> list[str]: + return ["text"] + + @property + def writes(self) -> list[str]: + return ["analysis"] app = ( ApplicationBuilder() - .with_graph(g) + .with_actions(parallel_analysis=ParallelTextAnalysis()) + .with_transitions(("parallel_analysis", "parallel_analysis")) # Or continue to next action .with_state(text="Sample text to analyze") - .with_entrypoint("start") + .with_entrypoint("parallel_analysis") .build() ) ``` @@ -622,8 +639,84 @@ async def test_async_action(): --- -For more examples, see the `examples/` directory in the Burr repository: +## Pydantic Typed State + +Use Pydantic models for type-safe state with IDE support and validation. + +```python +from pydantic import BaseModel, Field +from typing import Optional +from burr.core import action, ApplicationBuilder +from burr.integrations.pydantic import PydanticTypingSystem + +# Define state schema with Pydantic +class ChatState(BaseModel): + prompt: Optional[str] = Field(default=None, description="User prompt") + response: Optional[str] = Field(default=None, description="AI response") + chat_history: list[dict] = Field(default_factory=list, description="Conversation history") + +# Use @action.pydantic decorator +@action.pydantic(reads=[], writes=["prompt", "chat_history"]) +def human_input_typed(state: ChatState, prompt: str) -> ChatState: + """Capture user input with typed state.""" + # Access state as attributes (not brackets) + state.prompt = prompt + state.chat_history.append({"role": "user", "content": prompt}) + # Return the state object directly (not tuple) + return state + +@action.pydantic(reads=["chat_history"], writes=["response", "chat_history"]) +def ai_response_typed(state: ChatState) -> ChatState: + """Generate AI response with typed state.""" + # Read using attribute access + chat_history = state.chat_history + + # Process + response = call_llm(chat_history) + + # Mutate in-place (on internal copy) and return + state.response = response + state.chat_history.append({"role": "assistant", "content": response}) + return state + +# Configure application with typing system +app = ( + ApplicationBuilder() + .with_typing(PydanticTypingSystem(ChatState)) + .with_actions(human_input_typed, ai_response_typed) + .with_transitions( + ("human_input_typed", "ai_response_typed"), + ("ai_response_typed", "human_input_typed") + ) + .with_state(ChatState()) # Initialize with Pydantic model + .with_entrypoint("human_input_typed") + .build() +) + +# Run and access typed state +action, result, state = app.run( + halt_after=["ai_response_typed"], + inputs={"prompt": "Hello!"} +) + +# Access state data through .data property +print(state.data.response) # IDE autocomplete! +print(state.data.chat_history) +``` + +**Key differences from regular state:** +- Use `@action.pydantic` decorator instead of `@action` +- State parameter type is your Pydantic model (`ChatState`), not `State` +- Access with attributes (`state.prompt`) not brackets (`state["prompt"]`) +- Return state object directly, not `Tuple[dict, State]` +- Mutations happen in-place (on internal copy) +- Full IDE support: autocomplete, type checking, validation + +--- + +For more examples, see the `examples/` directory in the Apache Burr repository: - `examples/hello-world-counter/` - `examples/multi-modal-chatbot/` - `examples/conversational-rag/` - `examples/email-assistant/` +- `examples/typed-state/` diff --git a/.claude/skills/burr/patterns.md b/.claude/skills/burr/patterns.md index cf32b3005..c6c726b1a 100644 --- a/.claude/skills/burr/patterns.md +++ b/.claude/skills/burr/patterns.md @@ -19,7 +19,7 @@ under the License. # Apache Burr Design Patterns & Best Practices -Architectural guidance and best practices for building production-ready Burr applications. +Architectural guidance and best practices for building production-ready Apache Burr applications. ## Core Design Principles @@ -345,10 +345,13 @@ app = ( ### Pattern: Aggregating Parallel Results -Run multiple analyses in parallel and combine. +Run multiple analyses in parallel and combine using MapActions. ```python -from burr.core import graph +from burr.core.parallelism import MapActions +from burr.core import action, State, ApplicationContext +from typing import Dict, Any +from datetime import datetime @action(reads=["document"], writes=["summary"]) async def summarize(state: State) -> State: @@ -365,28 +368,43 @@ async def extract_topics(state: State) -> State: topics = await get_topics(state["document"]) return state.update(topics=topics) -@action(reads=["summary", "sentiment", "topics"], writes=["report"]) -def create_report(state: State) -> State: - """Aggregate all analyses into final report.""" - report = { - "summary": state["summary"], - "sentiment": state["sentiment"], - "topics": state["topics"], - "timestamp": datetime.now() - } - return state.update(report=report) +class ParallelDocumentAnalysis(MapActions): + async def actions(self, state: State, context: ApplicationContext, inputs: Dict[str, Any]): + yield summarize.with_name("summarize") + yield analyze_sentiment.with_name("analyze_sentiment") + yield extract_topics.with_name("extract_topics") + + async def state(self, state: State, inputs: Dict[str, Any]) -> State: + return state # Pass the same state to all actions + + async def reduce(self, state: State, states) -> State: + """Aggregate all analyses into final report.""" + report = {"timestamp": datetime.now()} + async for sub_state in states: + if "summary" in sub_state: + report["summary"] = sub_state["summary"] + if "sentiment" in sub_state: + report["sentiment"] = sub_state["sentiment"] + if "topics" in sub_state: + report["topics"] = sub_state["topics"] + return state.update(report=report) + + @property + def is_async(self) -> bool: + return True + + @property + def reads(self) -> list[str]: + return ["document"] + + @property + def writes(self) -> list[str]: + return ["report"] -g = ( - graph.GraphBuilder() - .with_actions(summarize, analyze_sentiment, extract_topics, create_report) - .with_transitions( - # Parallel execution - ("start", "summarize"), - ("start", "analyze_sentiment"), - ("start", "extract_topics"), - # Wait for all, then aggregate - (["summarize", "analyze_sentiment", "extract_topics"], "create_report") - ) +app = ( + ApplicationBuilder() + .with_actions(analyze_doc=ParallelDocumentAnalysis()) + .with_entrypoint("analyze_doc") .build() ) ``` @@ -535,8 +553,26 @@ state.update(ve="alice@example.com") **Use parallel execution for independent operations:** ```python -# Operations that don't depend on each other -("start", ["fetch_user", "fetch_products", "fetch_orders"]) +# Use MapActions to run independent operations in parallel +from burr.core.parallelism import MapActions + +class FetchAllData(MapActions): + def actions(self, state, context, inputs): + yield fetch_user.with_name("fetch_user") + yield fetch_products.with_name("fetch_products") + yield fetch_orders.with_name("fetch_orders") + + def state(self, state, inputs): + return state + + def reduce(self, state, states): + combined = {} + for s in states: + combined.update(s.get_all()) + return state.update(**combined) + + reads = [] + writes = ["user", "products", "orders"] ``` **Keep actions lightweight:** diff --git a/.claude/skills/burr/troubleshooting.md b/.claude/skills/burr/troubleshooting.md index da5111250..d2e25bda3 100644 --- a/.claude/skills/burr/troubleshooting.md +++ b/.claude/skills/burr/troubleshooting.md @@ -19,7 +19,7 @@ under the License. # Apache Burr Troubleshooting Guide -Common issues and solutions when working with Burr. +Common issues and solutions when working with Apache Burr. ## Installation Issues @@ -215,7 +215,19 @@ def increment(state: State) -> State: # ✅ Good - returns updated state @action(reads=["counter"], writes=["counter"]) def increment(state: State) -> State: - return state.update(counter=state["counter"] + 1) + result = {"counter": state["counter"] + 1} + return state.update(**result) +``` + +**Note:** You can return just `State` (shorthand) or `Tuple[dict, State]` (explicit): +```python +from typing import Tuple + +# Explicit pattern (recommended for clarity) +@action(reads=["counter"], writes=["counter"]) +def increment(state: State) -> Tuple[dict, State]: + result = {"counter": state["counter"] + 1} + return result, state.update(counter=result["counter"]) ``` 2. **Mutating state directly:** @@ -574,19 +586,37 @@ Application takes too long to execute 1. **Use parallel execution:** ```python -# Run independent actions in parallel -from burr.core import graph +# Run independent actions in parallel using MapActions +from burr.core.parallelism import MapActions +from burr.core import ApplicationContext +from typing import Dict, Any -g = ( - graph.GraphBuilder() - .with_actions(action1, action2, action3) - .with_transitions( - # These run in parallel - ("start", ["action1", "action2"]), - (["action1", "action2"], "action3") - ) - .build() -) +class ParallelActions(MapActions): + def actions(self, state: State, context: ApplicationContext, inputs: Dict[str, Any]): + yield action1.with_name("action1") + yield action2.with_name("action2") + + def state(self, state: State, inputs: Dict[str, Any]) -> State: + return state + + def reduce(self, state: State, states) -> State: + results = [s for s in states] + return state.update(parallel_results=results) + + @property + def reads(self) -> list[str]: + return [] + + @property + def writes(self) -> list[str]: + return ["parallel_results"] + +app = ApplicationBuilder().with_actions( + parallel=ParallelActions(), + action3=action3 +).with_transitions( + ("parallel", "action3") +).build() ``` 2. **Profile actions:** @@ -725,7 +755,7 @@ logging.basicConfig(level=logging.DEBUG) ``` When asking for help, include: -- Burr version: `pip show burr` +- Apache Burr version: `pip show burr` - Python version: `python --version` - Minimal code example that reproduces the issue - Full error message and traceback diff --git a/CLAUDE_SKILL.md b/CLAUDE_SKILL.md deleted file mode 100644 index 650c3da4a..000000000 --- a/CLAUDE_SKILL.md +++ /dev/null @@ -1,168 +0,0 @@ - - -# Apache Burr Claude Code Skill - -This repository includes a Claude Code skill that makes Claude an expert assistant for building Apache Burr applications. - -## Quick Install - -### Option 1: Install from GitHub (Easiest) - -```bash -# Install to personal skills directory -claude skill install https://github.com/apache/burr/.claude/skills/burr - -# Or install to project -cd your-project -claude skill install https://github.com/apache/burr/.claude/skills/burr --project -``` - -### Option 2: Manual Install - -```bash -# Clone the repository -git clone https://github.com/apache/burr - -# Install to personal skills directory -cp -r burr/.claude/skills/burr ~/.claude/skills/ - -# Or install to your project -cp -r burr/.claude/skills/burr .claude/skills/ -``` - -## What You Get - -Once installed, Claude becomes an expert in: - -- **Building Burr applications** - Get help scaffolding state machines from scratch -- **Writing actions** - Create properly structured action functions with correct decorators -- **Defining transitions** - Set up conditional logic and state machine flows -- **Adding observability** - Configure the Burr UI and tracking -- **Debugging issues** - Troubleshoot common problems with state machines -- **Following best practices** - Learn recommended patterns and anti-patterns -- **Code review** - Get feedback on your Burr code - -## Usage - -### Automatic Activation - -Just mention Burr in your conversation: - -``` -"Help me build a chatbot with Burr" -"Why isn't my state updating?" -"Show me how to add retry logic" -``` - -### Manual Invocation - -Use the `/burr` command explicitly: - -``` -/burr How do I create a streaming action? -/burr Review this code for best practices -/burr Show me an example of parallel execution -``` - -## What's Included - -The skill contains comprehensive documentation: - -- **SKILL.md** - Main instructions for Claude -- **api-reference.md** - Complete Burr API documentation -- **examples.md** - Working code examples for common patterns -- **patterns.md** - Best practices and design patterns -- **troubleshooting.md** - Solutions to common issues -- **README.md** - Installation and usage guide - -## Example Interactions - -**Building a new application:** -``` -You: "Help me create a Burr application for document processing" -Claude: I'll help you create a multi-stage pipeline... -[Generates complete application with actions and transitions] -``` - -**Getting examples:** -``` -You: "Show me how to implement retry logic in Burr" -Claude: Here's a retry pattern with error recovery... -[Provides working code example] -``` - -**Debugging:** -``` -You: "My state machine is looping infinitely" -Claude: Let me help you debug the transitions... -[Analyzes and suggests fixes] -``` - -**Code review:** -``` -You: "Review this Burr code for best practices" -Claude: Let me check your application... -[Provides detailed feedback] -``` - -## Documentation - -Full documentation available at: -- **Online**: https://burr.apache.org/getting_started/claude-skill -- **Local**: `docs/getting_started/claude-skill.rst` in this repository - -## Requirements - -- Claude Code CLI installed -- No Burr installation required (Claude can help you install when needed) - -## Customization - -You can customize the skill for your team: - -1. Edit the files in `.claude/skills/burr/` -2. Add your own examples to `examples.md` -3. Update patterns in `patterns.md` -4. Extend the API reference - -## Contributing - -Found a bug or want to improve the skill? - -- **Report issues**: https://github.com/apache/burr/issues -- **Submit fixes**: Open a pull request with your improvements -- **Suggest examples**: Share useful patterns you've discovered - -We welcome contributions of all sizes - from typo fixes to new examples! - -## Related Resources - -- **Burr Documentation**: https://burr.apache.org -- **GitHub Repository**: https://github.com/apache/burr -- **Example Applications**: `examples/` directory -- **Discord Community**: https://discord.gg/6Zy2DwP4f3 - -## License - -Apache License 2.0 - See LICENSE file in the root of the repository. - ---- - -Built with ❤️ by the Apache Burr community. diff --git a/README.md b/README.md index bc86c7ec8..dbc8ff908 100644 --- a/README.md +++ b/README.md @@ -158,7 +158,7 @@ cp -r /path/to/burr/.claude/skills/burr ~/.claude/skills/ Then just ask Claude naturally: *"Help me build a Burr application"* or use `/burr` for specific help. -See [CLAUDE_SKILL.md](CLAUDE_SKILL.md) for installation details and the [skill documentation](https://burr.apache.org/getting_started/claude-skill) for full usage guide. +See [.claude/skills/burr/README.md](.claude/skills/burr/README.md) for installation details and the [skill documentation](https://burr.apache.org/getting_started/claude-skill) for full usage guide. ## 📃 Comparison against common frameworks From 0949357d67bc754088b68ae755d9a8c7bc30a592 Mon Sep 17 00:00:00 2001 From: Stefan Krawczyk Date: Sat, 31 Jan 2026 12:43:08 -0800 Subject: [PATCH 3/7] Reorganize Claude Code plugin for marketplace distribution Restructures the Burr plugin from a flat skill structure to a nested marketplace-ready format that matches the Hamilton plugin organization. Changes: - Move from .claude/skills/burr/ to .claude/plugins/burr/ structure - Add .claude-plugin/ subdirectory with separated metadata: - plugin.json: References ./skills/ directory - marketplace.json: Marketplace distribution metadata - Add plugin-level documentation: - CHANGELOG.md: Version history and changes - README.md: Installation and usage guide - Nest skill content under skills/burr/ subdirectory - Remove embedded skill config from plugin.json This structure supports marketplace distribution, enables multiple skills per plugin, and follows Claude Code plugin best practices. --- .../burr/.claude-plugin/marketplace.json | 33 +++++++++ .../plugins/burr/.claude-plugin/plugin.json | 22 ++++++ .claude/plugins/burr/CHANGELOG.md | 24 +++++++ .claude/plugins/burr/README.md | 67 +++++++++++++++++++ .../{ => plugins/burr}/skills/burr/README.md | 0 .../{ => plugins/burr}/skills/burr/SKILL.md | 0 .../burr}/skills/burr/api-reference.md | 0 .../burr}/skills/burr/examples.md | 0 .../burr}/skills/burr/patterns.md | 0 .../burr}/skills/burr/troubleshooting.md | 0 .claude/skills/burr/plugin.json | 34 ---------- 11 files changed, 146 insertions(+), 34 deletions(-) create mode 100644 .claude/plugins/burr/.claude-plugin/marketplace.json create mode 100644 .claude/plugins/burr/.claude-plugin/plugin.json create mode 100644 .claude/plugins/burr/CHANGELOG.md create mode 100644 .claude/plugins/burr/README.md rename .claude/{ => plugins/burr}/skills/burr/README.md (100%) rename .claude/{ => plugins/burr}/skills/burr/SKILL.md (100%) rename .claude/{ => plugins/burr}/skills/burr/api-reference.md (100%) rename .claude/{ => plugins/burr}/skills/burr/examples.md (100%) rename .claude/{ => plugins/burr}/skills/burr/patterns.md (100%) rename .claude/{ => plugins/burr}/skills/burr/troubleshooting.md (100%) delete mode 100644 .claude/skills/burr/plugin.json diff --git a/.claude/plugins/burr/.claude-plugin/marketplace.json b/.claude/plugins/burr/.claude-plugin/marketplace.json new file mode 100644 index 000000000..2c818a366 --- /dev/null +++ b/.claude/plugins/burr/.claude-plugin/marketplace.json @@ -0,0 +1,33 @@ +{ + "name": "Burr Plugin Marketplace", + "description": "Official Claude Code plugin for the Apache Burr framework", + "version": "1.0.0", + "owner": { + "name": "Apache Software Foundation", + "url": "https://www.apache.org/" + }, + "plugins": [ + { + "name": "burr", + "source": "./", + "description": "Expert AI assistant for Apache Burr framework development - create state machines, define actions, manage transitions, and build stateful applications", + "version": "1.0.0", + "author": { + "name": "Apache Burr Contributors", + "email": "dev@burr.apache.org" + }, + "homepage": "https://burr.apache.org", + "repository": "https://github.com/apache/burr", + "license": "Apache-2.0", + "keywords": [ + "burr", + "state-machine", + "llm", + "agent", + "workflow", + "stateful", + "python" + ] + } + ] +} diff --git a/.claude/plugins/burr/.claude-plugin/plugin.json b/.claude/plugins/burr/.claude-plugin/plugin.json new file mode 100644 index 000000000..ca3fe0be3 --- /dev/null +++ b/.claude/plugins/burr/.claude-plugin/plugin.json @@ -0,0 +1,22 @@ +{ + "name": "burr", + "version": "1.0.0", + "description": "Expert AI assistant for Apache Burr framework development - create state machines, define actions, manage transitions, and build stateful applications", + "author": { + "name": "Apache Burr Contributors", + "email": "dev@burr.apache.org" + }, + "homepage": "https://burr.apache.org", + "repository": "https://github.com/apache/burr", + "license": "Apache-2.0", + "keywords": [ + "burr", + "state-machine", + "llm", + "agent", + "workflow", + "stateful", + "python" + ], + "skills": "./skills/" +} diff --git a/.claude/plugins/burr/CHANGELOG.md b/.claude/plugins/burr/CHANGELOG.md new file mode 100644 index 000000000..8dccc838c --- /dev/null +++ b/.claude/plugins/burr/CHANGELOG.md @@ -0,0 +1,24 @@ +# Changelog + +All notable changes to the Apache Burr Claude Code plugin will be documented in this file. + +The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), +and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). + +## [1.0.0] - 2026-01-31 + +### Added +- Initial release of the Apache Burr plugin for Claude Code +- Comprehensive skill documentation covering: + - Core API reference for actions, state machines, and application builders + - Common patterns and best practices + - Example implementations for various use cases + - Troubleshooting guide for common issues +- Marketplace-ready plugin structure +- Support for building stateful applications with state machines +- LLM agent development guidance +- Observability and tracking integration + +### Changed +- Reorganized plugin structure for marketplace distribution +- Migrated from flat skill structure to nested plugin/skills format diff --git a/.claude/plugins/burr/README.md b/.claude/plugins/burr/README.md new file mode 100644 index 000000000..419903e7b --- /dev/null +++ b/.claude/plugins/burr/README.md @@ -0,0 +1,67 @@ +# Apache Burr Plugin for Claude Code + +Official Claude Code plugin for the [Apache Burr](https://burr.apache.org) framework. + +## Overview + +This plugin provides expert AI assistance for building stateful applications using Apache Burr. Get help with state machines, actions, transitions, observability, and LLM agent development. + +## Features + +- **State Machine Design**: Expert guidance on designing state machines and workflows +- **Action Development**: Help writing actions with proper state management +- **Transition Logic**: Assistance with conditional transitions and graph structure +- **LLM Integration**: Support for building LLM agents and agentic workflows +- **Observability**: Guidance on tracking, telemetry, and debugging +- **Best Practices**: Learn common patterns and anti-patterns + +## Installation + +This plugin is included in the Apache Burr repository at `.claude/plugins/burr/`. + +To use it with Claude Code: + +1. Ensure you're in a project with the `.claude/plugins/` directory +2. The plugin will be automatically detected +3. Use `/burr` to activate the skill + +## Usage + +Once installed, you can invoke the Burr skill using: + +``` +/burr +``` + +The skill provides: +- API reference for core Burr concepts +- Code examples for common use cases +- Pattern guidance for stateful applications +- Troubleshooting help for common issues + +## Documentation + +The plugin includes comprehensive documentation: + +- **SKILL.md**: Main skill instructions and system prompt +- **api-reference.md**: Complete API documentation +- **examples.md**: Code examples and use cases +- **patterns.md**: Best practices and common patterns +- **troubleshooting.md**: Common issues and solutions + +## License + +Apache License 2.0 - See the main Burr repository for details. + +## Links + +- [Burr Documentation](https://burr.apache.org) +- [GitHub Repository](https://github.com/apache/burr) +- [Apache Software Foundation](https://www.apache.org/) + +## Support + +For issues or questions: +- Open an issue on [GitHub](https://github.com/apache/burr/issues) +- Check the [documentation](https://burr.apache.org) +- Review the troubleshooting guide included in this plugin diff --git a/.claude/skills/burr/README.md b/.claude/plugins/burr/skills/burr/README.md similarity index 100% rename from .claude/skills/burr/README.md rename to .claude/plugins/burr/skills/burr/README.md diff --git a/.claude/skills/burr/SKILL.md b/.claude/plugins/burr/skills/burr/SKILL.md similarity index 100% rename from .claude/skills/burr/SKILL.md rename to .claude/plugins/burr/skills/burr/SKILL.md diff --git a/.claude/skills/burr/api-reference.md b/.claude/plugins/burr/skills/burr/api-reference.md similarity index 100% rename from .claude/skills/burr/api-reference.md rename to .claude/plugins/burr/skills/burr/api-reference.md diff --git a/.claude/skills/burr/examples.md b/.claude/plugins/burr/skills/burr/examples.md similarity index 100% rename from .claude/skills/burr/examples.md rename to .claude/plugins/burr/skills/burr/examples.md diff --git a/.claude/skills/burr/patterns.md b/.claude/plugins/burr/skills/burr/patterns.md similarity index 100% rename from .claude/skills/burr/patterns.md rename to .claude/plugins/burr/skills/burr/patterns.md diff --git a/.claude/skills/burr/troubleshooting.md b/.claude/plugins/burr/skills/burr/troubleshooting.md similarity index 100% rename from .claude/skills/burr/troubleshooting.md rename to .claude/plugins/burr/skills/burr/troubleshooting.md diff --git a/.claude/skills/burr/plugin.json b/.claude/skills/burr/plugin.json deleted file mode 100644 index 5414da390..000000000 --- a/.claude/skills/burr/plugin.json +++ /dev/null @@ -1,34 +0,0 @@ -{ - "name": "burr", - "version": "1.0.0", - "description": "Apache Burr development assistant - expert guidance for building stateful applications with state machines", - "author": "Apache Burr Contributors", - "license": "Apache-2.0", - "repository": "https://github.com/apache/burr", - "homepage": "https://burr.apache.org", - "keywords": [ - "burr", - "state-machine", - "llm", - "agent", - "workflow", - "stateful", - "python" - ], - "skill": { - "name": "burr", - "description": "Helps developers build stateful applications using Apache Burr, including state machines, actions, transitions, and observability", - "files": [ - "SKILL.md", - "api-reference.md", - "examples.md", - "patterns.md", - "troubleshooting.md", - "README.md" - ] - }, - "install": { - "message": "Apache Burr skill installed! Use /burr to get expert help building state machine applications.", - "requirements": [] - } -} From b674da88e4e0e082399a497f06a33951987a956b Mon Sep 17 00:00:00 2001 From: Stefan Krawczyk Date: Sat, 31 Jan 2026 12:48:56 -0800 Subject: [PATCH 4/7] Add comprehensive state management patterns to skill documentation Adds detailed State Management Patterns section to patterns.md covering both regular dictionary-based state and Pydantic typed state patterns, bringing it in line with SKILL.md. --- .claude/plugins/burr/skills/burr/patterns.md | 110 +++++++++++++++++++ 1 file changed, 110 insertions(+) diff --git a/.claude/plugins/burr/skills/burr/patterns.md b/.claude/plugins/burr/skills/burr/patterns.md index c6c726b1a..3108dc190 100644 --- a/.claude/plugins/burr/skills/burr/patterns.md +++ b/.claude/plugins/burr/skills/burr/patterns.md @@ -153,6 +153,116 @@ def process_data(state: State) -> State: - Predictable behavior - Easier to reason about +## State Management Patterns + +### Regular State (Dictionary-Based) + +**Reading from state:** +```python +# Use bracket notation to access state values +value = state["key"] +chat_history = state["chat_history"] +counter = state["counter"] +``` + +**Updating state:** +State is immutable. Methods return NEW State objects: +```python +# state.update() - set/update keys, returns new State +new_state = state.update(counter=5, name="Alice") + +# state.append() - append to lists, returns new State +new_state = state.append(chat_history={"role": "user", "content": "hi"}) + +# state.increment() - increment numbers, returns new State +new_state = state.increment(counter=1) + +# Chaining - each method returns a State, enabling fluent patterns +new_state = state.update(prompt=prompt).append(chat_history=item) +``` + +**Action return pattern:** +Actions return `Tuple[dict, State]`: +```python +from typing import Tuple + +@action(reads=["prompt"], writes=["response", "chat_history"]) +def ai_respond(state: State) -> Tuple[dict, State]: + # 1. Read from state + prompt = state["prompt"] + + # 2. Process + response = call_llm(prompt) + + # 3. Return (result_dict, new_state) + # result_dict is exposed to callers/tracking + # new_state is the updated immutable state + return {"response": response}, state.update(response=response).append( + chat_history={"role": "assistant", "content": response} + ) +``` + +**Shorthand (also valid):** +```python +@action(reads=["counter"], writes=["counter"]) +def increment(state: State) -> State: + result = {"counter": state["counter"] + 1} + # Framework infers result from state updates + return state.update(**result) +``` + +### Pydantic Typed State (Different Pattern) + +**Define state model:** +```python +from pydantic import BaseModel, Field +from typing import Optional + +class ApplicationState(BaseModel): + prompt: Optional[str] = Field(default=None, description="User prompt") + response: Optional[str] = Field(default=None, description="AI response") + chat_history: list[dict] = Field(default_factory=list) +``` + +**Configure application:** +```python +from burr.integrations.pydantic import PydanticTypingSystem + +app = ( + ApplicationBuilder() + .with_typing(PydanticTypingSystem(ApplicationState)) + .with_state(ApplicationState()) + .build() +) +``` + +**Access typed state:** +```python +# Use attribute access (not bracket notation) +@action.pydantic(reads=["prompt"], writes=["response"]) +def ai_respond(state: ApplicationState) -> ApplicationState: + # 1. Read using attributes + prompt = state.prompt + + # 2. Process + response = call_llm(prompt) + + # 3. Mutate in-place and return state + # (Mutation happens on internal copy) + state.response = response + return state +``` + +**Key differences:** + +| Aspect | Regular State | Pydantic Typed State | +|--------|---------------|---------------------| +| **Access** | `state["key"]` | `state.key` | +| **Return** | `Tuple[dict, State]` | `ApplicationState` | +| **Decorator** | `@action(reads=[], writes=[])` | `@action.pydantic(reads=[], writes=[])` | +| **Updates** | Must use `.update()`, `.append()` | In-place mutation | +| **Type Safety** | Runtime only | IDE support + validation | + ## Common Patterns ### Pattern: Request-Response Cycle From 13ccfc4b52f1d98cb05789dea1384abfc9a07113 Mon Sep 17 00:00:00 2001 From: Stefan Krawczyk Date: Sun, 1 Feb 2026 07:51:22 +1100 Subject: [PATCH 5/7] Remove state immutability and deterministic actions sections Removed sections on state immutability and deterministic actions from patterns.md. --- .claude/plugins/burr/skills/burr/patterns.md | 60 -------------------- 1 file changed, 60 deletions(-) diff --git a/.claude/plugins/burr/skills/burr/patterns.md b/.claude/plugins/burr/skills/burr/patterns.md index 3108dc190..c57d3912d 100644 --- a/.claude/plugins/burr/skills/burr/patterns.md +++ b/.claude/plugins/burr/skills/burr/patterns.md @@ -93,66 +93,6 @@ def process_user(state: State) -> State: - Enables future optimizations - Catches errors early -### 3. State Immutability - -Never mutate state directly - always use `.update()` or `.append()`. - -**❌ Bad:** -```python -@action(reads=["items"], writes=["items"]) -def add_item(state: State, item: str) -> State: - items = state["items"] - items.append(item) # Mutates state! - return state -``` - -**✅ Good:** -```python -@action(reads=["items"], writes=["items"]) -def add_item(state: State, item: str) -> State: - return state.append(items=item) -``` - -**Benefits:** -- Time-travel debugging -- State history for replay -- Prevents subtle bugs -- Enables state persistence - -### 4. Deterministic Actions - -Given the same state and inputs, an action should always produce the same output. - -**❌ Bad - Non-deterministic:** -```python -@action(reads=["data"], writes=["result"]) -def process_data(state: State) -> State: - # Random behavior makes debugging impossible - if random.random() > 0.5: - result = transform_a(state["data"]) - else: - result = transform_b(state["data"]) - return state.update(result=result) -``` - -**✅ Good - Deterministic:** -```python -@action(reads=["data", "strategy"], writes=["result"]) -def process_data(state: State) -> State: - # Behavior controlled by state - if state["strategy"] == "a": - result = transform_a(state["data"]) - else: - result = transform_b(state["data"]) - return state.update(result=result) -``` - -**Benefits:** -- Reproducible debugging -- Testable code -- Predictable behavior -- Easier to reason about - ## State Management Patterns ### Regular State (Dictionary-Based) From 92cb8cae899f12ac87bc197c4ede046397cd305e Mon Sep 17 00:00:00 2001 From: Stefan Krawczyk Date: Sun, 1 Feb 2026 07:53:14 +1100 Subject: [PATCH 6/7] Reorganize best practices in SKILL.md Rearranged best practices for Apache Burr code to improve clarity. --- .claude/plugins/burr/skills/burr/SKILL.md | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/.claude/plugins/burr/skills/burr/SKILL.md b/.claude/plugins/burr/skills/burr/SKILL.md index 044d16843..a86d9cf46 100644 --- a/.claude/plugins/burr/skills/burr/SKILL.md +++ b/.claude/plugins/burr/skills/burr/SKILL.md @@ -287,12 +287,10 @@ def ai_respond(state: ApplicationState) -> ApplicationState: When writing or reviewing Apache Burr code: 1. **Type annotations**: Always use type hints for state and action parameters -2. **Action purity**: Actions should be deterministic given the same state -3. **State immutability**: Never mutate state directly, always use `.update()` or `.append()` -4. **Clear naming**: Action names should be verbs describing what they do -5. **Proper reads/writes**: Declare exactly what each action reads and writes -6. **Error handling**: Use try/except in actions and update state with error info -7. **Testing**: Write tests that verify state transitions and action outputs +2. **Clear naming**: Action names should be verbs describing what they do +3. **Proper reads/writes**: Declare exactly what each action reads and writes +4. **Error handling**: Use try/except in actions and update state with error info +5. **Testing**: Write tests that verify state transitions and action outputs ## Common Patterns to Recommend From 2e90eaf06a604de68394df524204cd51c9ac646b Mon Sep 17 00:00:00 2001 From: Stefan Krawczyk Date: Sun, 1 Feb 2026 07:56:08 +1100 Subject: [PATCH 7/7] Delete multi-source/target transitions section Removed section on multi-source/target transitions from API reference. --- .claude/plugins/burr/skills/burr/api-reference.md | 14 -------------- 1 file changed, 14 deletions(-) diff --git a/.claude/plugins/burr/skills/burr/api-reference.md b/.claude/plugins/burr/skills/burr/api-reference.md index 58bc17aa0..939959abd 100644 --- a/.claude/plugins/burr/skills/burr/api-reference.md +++ b/.claude/plugins/burr/skills/burr/api-reference.md @@ -327,20 +327,6 @@ from burr.core import expr ("counter", "done", default) ``` -### Multi-source/target Transitions - -Transition from multiple sources to multiple targets: - -```python -# Multiple sources to one target -(["action1", "action2"], "action3") - -# One source to multiple targets (conditional branching - only one executes) -("start", ["parallel1", "parallel2"]) - -# For actual parallelism, use MapActions/MapStates/MapActionsAndStates -``` - ## Application The built application instance provides methods to execute and inspect the state machine.