# Cadence SDK
A comprehensive SDK for building custom AI agent plugins for the Cadence Framework.
## Overview
The Cadence SDK provides the tools and interfaces needed to create powerful, extensible AI agents that integrate
seamlessly with the Cadence multi-agent framework. Build agents with custom tools, sophisticated reasoning capabilities,
and domain-specific knowledge.
## Features
- **Agent Framework**: Create intelligent agents with custom behavior and system prompts
- **Tool System**: Build and integrate custom tools using the `@tool` decorator
- **Plugin Management**: Easy plugin discovery and registration with automatic loading
- **Type Safety**: Full Python type support with proper annotations
- **Extensible**: Plugin-based architecture for easy extension and customization
- **LangGraph Integration**: Seamless integration with LangGraph workflows
- **LLM Binding**: Automatic tool binding to language models
## Installation
```bash
pip install cadence-sdk
```
## Quick Start
### Key Imports
```python
# Core classes - import from main SDK module (recommended)
from cadence_sdk import BaseAgent, BasePlugin, PluginMetadata, tool, register_plugin
# Alternative: import specific components if needed
from cadence_sdk.base.agent import BaseAgent
from cadence_sdk.base.plugin import BasePlugin
from cadence_sdk.base.metadata import PluginMetadata
from cadence_sdk.tools.decorators import tool
from cadence_sdk import register_plugin, discover_plugins
```
**Note**: The main import approach is recommended for most use cases as it provides all necessary components in one
import statement.
### Creating a Simple Agent
```python
from cadence_sdk import BaseAgent, PluginMetadata, tool
class CalculatorAgent(BaseAgent):
def __init__(self, metadata: PluginMetadata):
super().__init__(metadata)
def get_tools(self):
from .tools import math_tools
return math_tools
def get_system_prompt(self) -> str:
return "You are a calculator agent that helps with mathematical calculations."
@tool
def calculate(expression: str) -> str:
"""Perform mathematical calculations"""
try:
result = eval(expression)
return str(result)
except Exception as e:
return f"Error: {str(e)}"
```
### Plugin Structure
```
my_plugin/
├── __init__.py # Plugin registration with register_plugin()
├── plugin.py # Main plugin class (BasePlugin)
├── agent.py # Agent implementation (BaseAgent)
├── tools.py # Tool functions with @tool decorator
├── pyproject.toml # Package configuration
└── README.md # Documentation
```
**Required Files:**
- `__init__.py`: Must call `register_plugin(YourPlugin)` to auto-register the plugin
- `plugin.py`: Must implement `BasePlugin` with `get_metadata()` and `create_agent()` methods
- `agent.py`: Must implement `BaseAgent` with `get_tools()` and `get_system_prompt()` methods
- `tools.py`: Contains tool functions decorated with `@tool` decorator
- `pyproject.toml`: Package metadata and dependencies
### Plugin Registration
```python
from cadence_sdk import BasePlugin, PluginMetadata
class CalculatorPlugin(BasePlugin):
@staticmethod
def get_metadata() -> PluginMetadata:
return PluginMetadata(
name="calculator",
version="1.0.7",
description="Mathematical calculation plugin",
capabilities=["mathematics", "calculations"],
llm_requirements={
"provider": "openai",
"model": "gpt-4",
"temperature": 0.1
},
agent_type="specialized",
dependencies=["cadence_sdk>=1.0.2,<2.0.0"]
)
@staticmethod
def create_agent():
from .agent import CalculatorAgent
return CalculatorAgent(CalculatorPlugin.get_metadata())
```
## Configuration
### Plugin Registration
To make your plugin discoverable by the Cadence framework, you need to register it in your plugin's `__init__.py`:
```python
# plugins/src/cadence_example_plugins/my_plugin/__init__.py
from cadence_sdk import register_plugin
from .plugin import MyPlugin
# Register on import
register_plugin(MyPlugin)
```
### Environment Variables
```bash
# Set plugin directories (single path)
export CADENCE_PLUGINS_DIR="./plugins/src/cadence_plugins"
# Or multiple directories as JSON array
export CADENCE_PLUGINS_DIR='["/path/to/plugins", "/another/path"]'
# Plugin limits (configured in main application)
export CADENCE_MAX_AGENT_HOPS=25
export CADENCE_GRAPH_RECURSION_LIMIT=50
# LLM Provider Configuration
export CADENCE_DEFAULT_LLM_PROVIDER=openai
export CADENCE_OPENAI_API_KEY=your-api-key
```
### Plugin Discovery
The SDK automatically discovers plugins from:
- **Environment packages**: Pip-installed packages that depend on `cadence_sdk`
- **Directory paths**: File system directories specified in `CADENCE_PLUGINS_DIR`
- **Custom registries**: Programmatic plugin registration via `register_plugin()`
**Auto-registration**: When a plugin package is imported, it automatically calls `register_plugin()` to make itself
available to the framework.
## Advanced Usage
### Custom Tool Decorators
```python
from cadence_sdk import tool
@tool
def weather_tool(city: str) -> str:
"""Get weather information for a city."""
# Implementation here
return f"Weather for {city}: Sunny, 72°F"
# Tools are automatically registered when using the decorator
weather_tools = [weather_tool]
```
### Parallel Tool Calls Support
BaseAgent supports parallel tool execution, allowing multiple tools to be called simultaneously for improved performance
and efficiency:
```python
from cadence_sdk import BaseAgent, PluginMetadata
class ParallelAgent(BaseAgent):
def __init__(self, metadata: PluginMetadata):
# Enable parallel tool calls (default: True)
super().__init__(metadata, parallel_tool_calls=True)
def get_tools(self):
return [tool1, tool2, tool3]
def get_system_prompt(self) -> str:
return "You are an agent that can execute multiple tools in parallel."
class SequentialAgent(BaseAgent):
def __init__(self, metadata: PluginMetadata):
# Disable parallel tool calls for sequential execution
super().__init__(metadata, parallel_tool_calls=False)
def get_tools(self):
return [tool1, tool2, tool3]
def get_system_prompt(self) -> str:
return "You are an agent that executes tools sequentially."
```
**Benefits of Parallel Tool Calls:**
- **Improved Performance**: Multiple tools execute concurrently instead of sequentially
- **Better User Experience**: Faster response times for multi-step operations
- **Resource Optimization**: Efficient use of computational resources
- **Scalability**: Better handling of complex, multi-tool workflows
**When to Use Parallel Tool Calls:**
- ✅ **Enable** when tools are independent and can run concurrently
- ✅ **Enable** for performance-critical operations
- ✅ **Enable** for I/O-bound operations (API calls, database queries, file operations)
- ✅ **Disable** when tools have dependencies or shared resources
- ✅ **Disable** when tools modify shared state sequentially
- ✅ **Disable** for debugging and troubleshooting
### Agent State Management
```python
from cadence_sdk import BaseAgent, PluginMetadata
class StatefulAgent(BaseAgent):
def __init__(self, metadata: PluginMetadata):
# Enable parallel tool calls (default behavior)
super().__init__(metadata, parallel_tool_calls=True)
def get_tools(self):
return []
def get_system_prompt(self) -> str:
return "You are a stateful agent that maintains context."
@staticmethod
def should_continue(state: dict) -> str:
"""Enhanced routing decision - decide whether to continue or return to coordinator.
This is the REAL implementation from the Cadence SDK - it's much simpler than you might expect!
The method simply checks if the agent's response has tool calls and routes accordingly.
"""
last_msg = state.get("messages", [])[-1] if state.get("messages") else None
if not last_msg:
return "back"
tool_calls = getattr(last_msg, "tool_calls", None)
return "continue" if tool_calls else "back"
```
**Enhanced Routing System**: The `should_continue` method allows agents to control workflow flow by returning:
- `"continue"`: Keep processing with current agent (has tool calls)
- `"back"`: Return control to the coordinator (no tool calls)
**Key Benefits:**
- **Intelligent Decision Making**: Agents automatically decide routing based on their responses
- **Consistent Flow**: All responses go through the same routing path
- **No Circular Routing**: Eliminated infinite loops through proper edge configuration
- **Better Debugging**: Clear routing decisions and comprehensive logging
- **Predictable Behavior**: System behavior is more predictable and maintainable
### Plugin Registry
```python
from cadence_sdk import PluginRegistry
# Get plugin registry
registry = PluginRegistry()
# Register custom plugin
registry.register(CalculatorPlugin())
# Discover plugins
plugins = registry.discover()
# Get specific plugin
calculator_plugin = registry.get_plugin("calculator")
```
**Registry Features**: The plugin registry provides:
- Automatic plugin discovery and loading
- Plugin validation and health checks
- Metadata access and plugin management
- Integration with the main Cadence framework
## Examples
### Math Agent
```python
from cadence_sdk import BaseAgent, PluginMetadata, tool
class MathAgent(BaseAgent):
def __init__(self, metadata: PluginMetadata):
# Enable parallel tool calls for concurrent calculations
super().__init__(metadata, parallel_tool_calls=True)
def get_tools(self):
from .tools import math_tools
return math_tools
def get_system_prompt(self) -> str:
return "You are a math agent specialized in mathematical operations. Use the calculator tool for calculations."
@staticmethod
def should_continue(state: dict) -> str:
"""Enhanced routing decision - decide whether to continue or return to coordinator."""
last_msg = state.get("messages", [])[-1] if state.get("messages") else None
if not last_msg:
return "back"
tool_calls = getattr(last_msg, "tool_calls", None)
return "continue" if tool_calls else "back"
@tool
def calculate(expression: str) -> str:
"""Perform mathematical calculations"""
try:
result = eval(expression)
return f"Result: {result}"
except Exception as e:
return f"Invalid expression: {str(e)}"
@tool
def add(a: int, b: int) -> int:
"""Add two numbers together"""
return a + b
math_tools = [calculate, add]
```
### Search Agent
```python
from cadence_sdk import BaseAgent, PluginMetadata, tool
import requests
class SearchAgent(BaseAgent):
def __init__(self, metadata: PluginMetadata):
# Enable parallel tool calls for concurrent search operations
super().__init__(metadata, parallel_tool_calls=True)
def get_tools(self):
from .tools import search_tools
return search_tools
def get_system_prompt(self) -> str:
return "You are a search agent that helps users find information on the web. Use the web search tool to perform searches."
@staticmethod
def should_continue(state: dict) -> str:
"""Enhanced routing decision - decide whether to continue or return to coordinator."""
last_msg = state.get("messages", [])[-1] if state.get("messages") else None
if not last_msg:
return "back"
tool_calls = getattr(last_msg, "tool_calls", None)
return "continue" if tool_calls else "back"
@tool
def web_search(query: str) -> str:
"""Search the web for information"""
# Implementation would go here
return f"Searching for: {query}"
@tool
def news_search(topic: str) -> str:
"""Search for news about a specific topic"""
# Implementation would go here
return f"Searching for news about: {topic}"
search_tools = [web_search, news_search]
```
## Best Practices
### Plugin Design Guidelines
1. **Single Responsibility**: Each plugin should focus on one specific domain or capability
2. **Clear Naming**: Use descriptive names for plugins, agents, and tools
3. **Proper Error Handling**: Always handle exceptions in tool functions
4. **Documentation**: Provide clear docstrings for all tools and methods
5. **Type Hints**: Use proper type annotations for better code quality
6. **Testing**: Include unit tests for your tools and agent logic
7. **Enhanced Routing**: Implement the `should_continue` method for intelligent routing decisions
8. **Consistent Flow**: Use fake tool calls when agents answer directly to maintain routing consistency
9. **Parallel Tool Calls**: Configure `parallel_tool_calls` parameter based on your tools' execution requirements
### Enhanced Routing Best Practices
```python
class EnhancedAgent(BaseAgent):
@staticmethod
def should_continue(state: dict) -> str:
"""Implement intelligent routing decisions based on agent response.
This is the REAL implementation from the Cadence SDK - it's much simpler than you might expect!
The method simply checks if the agent's response has tool calls and routes accordingly.
"""
last_msg = state.get("messages", [])[-1] if state.get("messages") else None
if not last_msg:
return "back"
tool_calls = getattr(last_msg, "tool_calls", None)
return "continue" if tool_calls else "back"
```
**Important Implementation Notes:**
- **`should_continue` must be a static method**: Use `@staticmethod` decorator
- **The SDK automatically handles fake tool calls**: When agents answer directly, fake "back" tool calls are created
automatically
- **No manual fake tool call creation needed**: The system handles this transparently
**Routing Guidelines:**
- **Always implement `should_continue`**: This method controls the conversation flow
- **Return "continue" for tool calls**: When agent generates tool calls, route to tools
- **Return "back" for direct answers**: When agent answers directly, return to coordinator
- **Use fake tool calls**: The system automatically creates fake "back" tool calls for consistency
- **Test both scenarios**: Ensure your agent works with and without tool calls
### Common Patterns
```python
# Tool function with proper error handling
@tool
def safe_operation(input_data: str) -> str:
"""Perform a safe operation with error handling."""
try:
# Your logic here
result = process_data(input_data)
return f"Success: {result}"
except Exception as e:
return f"Error: {str(e)}"
# Agent with comprehensive tool collection
class ComprehensiveAgent(BaseAgent):
def get_tools(self):
from .tools import (
primary_tools,
utility_tools,
validation_tools
)
return primary_tools + utility_tools + validation_tools
def get_system_prompt(self) -> str:
return (
"You are a comprehensive agent with multiple capabilities. "
"Use the appropriate tools based on the user's request. "
"Always explain your reasoning and show your work."
)
```
## Development
### Setting up Development Environment
```bash
# Clone the main repository
git clone https://github.com/jonaskahn/cadence.git
cd cadence
# Install SDK dependencies
cd sdk
poetry install
# Run tests
poetry run pytest
# Format code
poetry run black src/
poetry run isort src/
```
### Testing
```bash
# Run all tests
poetry run pytest
# Run with coverage
poetry run pytest --cov=src/cadence_sdk
# Run specific test categories
poetry run pytest -m "unit"
poetry run pytest -m "integration"
```
## Contributing
We welcome contributions! Please see our [Contributing Guide](CONTRIBUTING.md) for details.
### Development Setup
1. Fork the repository
2. Create a feature branch
3. Make your changes
4. Add tests
5. Submit a pull request
## License
This project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details.
## Troubleshooting
### Common Issues
1. **Plugin Not Loading**: Ensure `register_plugin()` is called in `__init__.py`
2. **Import Errors**: Check that `cadence_sdk` is properly installed and imported
3. **Tool Registration**: Verify tools are decorated with `@tool` and included in the tools list
4. **Metadata Issues**: Ensure all required fields are provided in `PluginMetadata`
### Debug Tips
```python
# Enable debug logging
import logging
logging.basicConfig(level=logging.DEBUG)
# Check plugin registration
from cadence_sdk import discover_plugins
plugins = discover_plugins()
print(f"Discovered plugins: {[p.name for p in plugins]}")
# Verify tool decoration
from .tools import my_tool
print(f"Tool type: {type(my_tool)}")
print(f"Tool name: {getattr(my_tool, 'name', 'No name')}")
```
## Support
- **Documentation**: [Read the Docs](https://cadence.readthedocs.io/)
- **Issues**: [GitHub Issues](https://github.com/jonaskahn/cadence/issues)
- **Discussions**: [GitHub Discussions](https://github.com/jonaskahn/cadence/discussions)
## Quick Reference
### Essential Imports
```python
from cadence_sdk import BaseAgent, BasePlugin, PluginMetadata, tool, register_plugin
```
### Required Methods
- **Plugin**: `get_metadata()`, `create_agent()`
- **Agent**: `get_tools()`, `get_system_prompt()`
- **Tools**: Use `@tool` decorator
### File Structure
```
my_plugin/
├── __init__.py # register_plugin(MyPlugin)
├── plugin.py # BasePlugin implementation
├── agent.py # BaseAgent implementation
└── tools.py # @tool decorated functions
```
### Environment Variables
```bash
export CADENCE_PLUGINS_DIR="./plugins"
export CADENCE_DEFAULT_LLM_PROVIDER=openai
export CADENCE_OPENAI_API_KEY=your-key
```
---
**Built with ❤️ for the Cadence AI community**
Raw data
{
"_id": null,
"home_page": "https://github.com/jonaskahn/cadence-sdk",
"name": "cadence-sdk",
"maintainer": null,
"docs_url": null,
"requires_python": "<3.14,>=3.13",
"maintainer_email": null,
"keywords": "ai, agents, cadence-ai, cadence_sdk, langchain, langgraph, plugins",
"author": "Jonas Kahn",
"author_email": "me@ifelse.one",
"download_url": "https://files.pythonhosted.org/packages/25/a6/df450a392ec3a7ed3d3094bc220bccf7d95b44a0b30f4d77b4e5dc4b3b00/cadence_sdk-1.0.7.tar.gz",
"platform": null,
"description": "# Cadence SDK\n\nA comprehensive SDK for building custom AI agent plugins for the Cadence Framework.\n\n## Overview\n\nThe Cadence SDK provides the tools and interfaces needed to create powerful, extensible AI agents that integrate\nseamlessly with the Cadence multi-agent framework. Build agents with custom tools, sophisticated reasoning capabilities,\nand domain-specific knowledge.\n\n## Features\n\n- **Agent Framework**: Create intelligent agents with custom behavior and system prompts\n- **Tool System**: Build and integrate custom tools using the `@tool` decorator\n- **Plugin Management**: Easy plugin discovery and registration with automatic loading\n- **Type Safety**: Full Python type support with proper annotations\n- **Extensible**: Plugin-based architecture for easy extension and customization\n- **LangGraph Integration**: Seamless integration with LangGraph workflows\n- **LLM Binding**: Automatic tool binding to language models\n\n## Installation\n\n```bash\npip install cadence-sdk\n```\n\n## Quick Start\n\n### Key Imports\n\n```python\n# Core classes - import from main SDK module (recommended)\nfrom cadence_sdk import BaseAgent, BasePlugin, PluginMetadata, tool, register_plugin\n\n# Alternative: import specific components if needed\nfrom cadence_sdk.base.agent import BaseAgent\nfrom cadence_sdk.base.plugin import BasePlugin\nfrom cadence_sdk.base.metadata import PluginMetadata\nfrom cadence_sdk.tools.decorators import tool\nfrom cadence_sdk import register_plugin, discover_plugins\n```\n\n**Note**: The main import approach is recommended for most use cases as it provides all necessary components in one\nimport statement.\n\n### Creating a Simple Agent\n\n```python\nfrom cadence_sdk import BaseAgent, PluginMetadata, tool\n\n\nclass CalculatorAgent(BaseAgent):\n def __init__(self, metadata: PluginMetadata):\n super().__init__(metadata)\n\n def get_tools(self):\n from .tools import math_tools\n return math_tools\n\n def get_system_prompt(self) -> str:\n return \"You are a calculator agent that helps with mathematical calculations.\"\n\n\n@tool\ndef calculate(expression: str) -> str:\n \"\"\"Perform mathematical calculations\"\"\"\n try:\n result = eval(expression)\n return str(result)\n except Exception as e:\n return f\"Error: {str(e)}\"\n```\n\n### Plugin Structure\n\n```\nmy_plugin/\n\u251c\u2500\u2500 __init__.py # Plugin registration with register_plugin()\n\u251c\u2500\u2500 plugin.py # Main plugin class (BasePlugin)\n\u251c\u2500\u2500 agent.py # Agent implementation (BaseAgent)\n\u251c\u2500\u2500 tools.py # Tool functions with @tool decorator\n\u251c\u2500\u2500 pyproject.toml # Package configuration\n\u2514\u2500\u2500 README.md # Documentation\n```\n\n**Required Files:**\n\n- `__init__.py`: Must call `register_plugin(YourPlugin)` to auto-register the plugin\n- `plugin.py`: Must implement `BasePlugin` with `get_metadata()` and `create_agent()` methods\n- `agent.py`: Must implement `BaseAgent` with `get_tools()` and `get_system_prompt()` methods\n- `tools.py`: Contains tool functions decorated with `@tool` decorator\n- `pyproject.toml`: Package metadata and dependencies\n\n### Plugin Registration\n\n```python\nfrom cadence_sdk import BasePlugin, PluginMetadata\n\n\nclass CalculatorPlugin(BasePlugin):\n @staticmethod\n def get_metadata() -> PluginMetadata:\n return PluginMetadata(\n name=\"calculator\",\n version=\"1.0.7\",\n description=\"Mathematical calculation plugin\",\n capabilities=[\"mathematics\", \"calculations\"],\n llm_requirements={\n \"provider\": \"openai\",\n \"model\": \"gpt-4\",\n \"temperature\": 0.1\n },\n agent_type=\"specialized\",\n dependencies=[\"cadence_sdk>=1.0.2,<2.0.0\"]\n )\n\n @staticmethod\n def create_agent():\n from .agent import CalculatorAgent\n return CalculatorAgent(CalculatorPlugin.get_metadata())\n```\n\n## Configuration\n\n### Plugin Registration\n\nTo make your plugin discoverable by the Cadence framework, you need to register it in your plugin's `__init__.py`:\n\n```python\n# plugins/src/cadence_example_plugins/my_plugin/__init__.py\nfrom cadence_sdk import register_plugin\nfrom .plugin import MyPlugin\n\n# Register on import\nregister_plugin(MyPlugin)\n```\n\n### Environment Variables\n\n```bash\n# Set plugin directories (single path)\nexport CADENCE_PLUGINS_DIR=\"./plugins/src/cadence_plugins\"\n\n# Or multiple directories as JSON array\nexport CADENCE_PLUGINS_DIR='[\"/path/to/plugins\", \"/another/path\"]'\n\n# Plugin limits (configured in main application)\nexport CADENCE_MAX_AGENT_HOPS=25\n\nexport CADENCE_GRAPH_RECURSION_LIMIT=50\n\n# LLM Provider Configuration\nexport CADENCE_DEFAULT_LLM_PROVIDER=openai\nexport CADENCE_OPENAI_API_KEY=your-api-key\n```\n\n### Plugin Discovery\n\nThe SDK automatically discovers plugins from:\n\n- **Environment packages**: Pip-installed packages that depend on `cadence_sdk`\n- **Directory paths**: File system directories specified in `CADENCE_PLUGINS_DIR`\n- **Custom registries**: Programmatic plugin registration via `register_plugin()`\n\n**Auto-registration**: When a plugin package is imported, it automatically calls `register_plugin()` to make itself\navailable to the framework.\n\n## Advanced Usage\n\n### Custom Tool Decorators\n\n```python\nfrom cadence_sdk import tool\n\n\n@tool\ndef weather_tool(city: str) -> str:\n \"\"\"Get weather information for a city.\"\"\"\n # Implementation here\n return f\"Weather for {city}: Sunny, 72\u00b0F\"\n\n\n# Tools are automatically registered when using the decorator\nweather_tools = [weather_tool]\n```\n\n### Parallel Tool Calls Support\n\nBaseAgent supports parallel tool execution, allowing multiple tools to be called simultaneously for improved performance\nand efficiency:\n\n```python\nfrom cadence_sdk import BaseAgent, PluginMetadata\n\n\nclass ParallelAgent(BaseAgent):\n def __init__(self, metadata: PluginMetadata):\n # Enable parallel tool calls (default: True)\n super().__init__(metadata, parallel_tool_calls=True)\n\n def get_tools(self):\n return [tool1, tool2, tool3]\n\n def get_system_prompt(self) -> str:\n return \"You are an agent that can execute multiple tools in parallel.\"\n\n\nclass SequentialAgent(BaseAgent):\n def __init__(self, metadata: PluginMetadata):\n # Disable parallel tool calls for sequential execution\n super().__init__(metadata, parallel_tool_calls=False)\n\n def get_tools(self):\n return [tool1, tool2, tool3]\n\n def get_system_prompt(self) -> str:\n return \"You are an agent that executes tools sequentially.\"\n```\n\n**Benefits of Parallel Tool Calls:**\n\n- **Improved Performance**: Multiple tools execute concurrently instead of sequentially\n- **Better User Experience**: Faster response times for multi-step operations\n- **Resource Optimization**: Efficient use of computational resources\n- **Scalability**: Better handling of complex, multi-tool workflows\n\n**When to Use Parallel Tool Calls:**\n\n- \u2705 **Enable** when tools are independent and can run concurrently\n- \u2705 **Enable** for performance-critical operations\n- \u2705 **Enable** for I/O-bound operations (API calls, database queries, file operations)\n- \u2705 **Disable** when tools have dependencies or shared resources\n- \u2705 **Disable** when tools modify shared state sequentially\n- \u2705 **Disable** for debugging and troubleshooting\n\n### Agent State Management\n\n```python\nfrom cadence_sdk import BaseAgent, PluginMetadata\n\n\nclass StatefulAgent(BaseAgent):\n def __init__(self, metadata: PluginMetadata):\n # Enable parallel tool calls (default behavior)\n super().__init__(metadata, parallel_tool_calls=True)\n\n def get_tools(self):\n return []\n\n def get_system_prompt(self) -> str:\n return \"You are a stateful agent that maintains context.\"\n\n @staticmethod\n def should_continue(state: dict) -> str:\n \"\"\"Enhanced routing decision - decide whether to continue or return to coordinator.\n\n This is the REAL implementation from the Cadence SDK - it's much simpler than you might expect!\n The method simply checks if the agent's response has tool calls and routes accordingly.\n \"\"\"\n last_msg = state.get(\"messages\", [])[-1] if state.get(\"messages\") else None\n if not last_msg:\n return \"back\"\n\n tool_calls = getattr(last_msg, \"tool_calls\", None)\n return \"continue\" if tool_calls else \"back\"\n```\n\n**Enhanced Routing System**: The `should_continue` method allows agents to control workflow flow by returning:\n\n- `\"continue\"`: Keep processing with current agent (has tool calls)\n- `\"back\"`: Return control to the coordinator (no tool calls)\n\n**Key Benefits:**\n\n- **Intelligent Decision Making**: Agents automatically decide routing based on their responses\n- **Consistent Flow**: All responses go through the same routing path\n- **No Circular Routing**: Eliminated infinite loops through proper edge configuration\n- **Better Debugging**: Clear routing decisions and comprehensive logging\n- **Predictable Behavior**: System behavior is more predictable and maintainable\n\n### Plugin Registry\n\n```python\nfrom cadence_sdk import PluginRegistry\n\n# Get plugin registry\nregistry = PluginRegistry()\n\n# Register custom plugin\nregistry.register(CalculatorPlugin())\n\n# Discover plugins\nplugins = registry.discover()\n\n# Get specific plugin\ncalculator_plugin = registry.get_plugin(\"calculator\")\n```\n\n**Registry Features**: The plugin registry provides:\n\n- Automatic plugin discovery and loading\n- Plugin validation and health checks\n- Metadata access and plugin management\n- Integration with the main Cadence framework\n\n## Examples\n\n### Math Agent\n\n```python\nfrom cadence_sdk import BaseAgent, PluginMetadata, tool\n\n\nclass MathAgent(BaseAgent):\n def __init__(self, metadata: PluginMetadata):\n # Enable parallel tool calls for concurrent calculations\n super().__init__(metadata, parallel_tool_calls=True)\n\n def get_tools(self):\n from .tools import math_tools\n return math_tools\n\n def get_system_prompt(self) -> str:\n return \"You are a math agent specialized in mathematical operations. Use the calculator tool for calculations.\"\n\n @staticmethod\n def should_continue(state: dict) -> str:\n \"\"\"Enhanced routing decision - decide whether to continue or return to coordinator.\"\"\"\n last_msg = state.get(\"messages\", [])[-1] if state.get(\"messages\") else None\n if not last_msg:\n return \"back\"\n\n tool_calls = getattr(last_msg, \"tool_calls\", None)\n return \"continue\" if tool_calls else \"back\"\n\n\n@tool\ndef calculate(expression: str) -> str:\n \"\"\"Perform mathematical calculations\"\"\"\n try:\n result = eval(expression)\n return f\"Result: {result}\"\n except Exception as e:\n return f\"Invalid expression: {str(e)}\"\n\n\n@tool\ndef add(a: int, b: int) -> int:\n \"\"\"Add two numbers together\"\"\"\n return a + b\n\n\nmath_tools = [calculate, add]\n```\n\n### Search Agent\n\n```python\nfrom cadence_sdk import BaseAgent, PluginMetadata, tool\nimport requests\n\n\nclass SearchAgent(BaseAgent):\n def __init__(self, metadata: PluginMetadata):\n # Enable parallel tool calls for concurrent search operations\n super().__init__(metadata, parallel_tool_calls=True)\n\n def get_tools(self):\n from .tools import search_tools\n return search_tools\n\n def get_system_prompt(self) -> str:\n return \"You are a search agent that helps users find information on the web. Use the web search tool to perform searches.\"\n\n @staticmethod\n def should_continue(state: dict) -> str:\n \"\"\"Enhanced routing decision - decide whether to continue or return to coordinator.\"\"\"\n last_msg = state.get(\"messages\", [])[-1] if state.get(\"messages\") else None\n if not last_msg:\n return \"back\"\n\n tool_calls = getattr(last_msg, \"tool_calls\", None)\n return \"continue\" if tool_calls else \"back\"\n\n\n@tool\ndef web_search(query: str) -> str:\n \"\"\"Search the web for information\"\"\"\n # Implementation would go here\n return f\"Searching for: {query}\"\n\n\n@tool\ndef news_search(topic: str) -> str:\n \"\"\"Search for news about a specific topic\"\"\"\n # Implementation would go here\n return f\"Searching for news about: {topic}\"\n\n\nsearch_tools = [web_search, news_search]\n```\n\n## Best Practices\n\n### Plugin Design Guidelines\n\n1. **Single Responsibility**: Each plugin should focus on one specific domain or capability\n2. **Clear Naming**: Use descriptive names for plugins, agents, and tools\n3. **Proper Error Handling**: Always handle exceptions in tool functions\n4. **Documentation**: Provide clear docstrings for all tools and methods\n5. **Type Hints**: Use proper type annotations for better code quality\n6. **Testing**: Include unit tests for your tools and agent logic\n7. **Enhanced Routing**: Implement the `should_continue` method for intelligent routing decisions\n8. **Consistent Flow**: Use fake tool calls when agents answer directly to maintain routing consistency\n9. **Parallel Tool Calls**: Configure `parallel_tool_calls` parameter based on your tools' execution requirements\n\n### Enhanced Routing Best Practices\n\n```python\nclass EnhancedAgent(BaseAgent):\n @staticmethod\n def should_continue(state: dict) -> str:\n \"\"\"Implement intelligent routing decisions based on agent response.\n\n This is the REAL implementation from the Cadence SDK - it's much simpler than you might expect!\n The method simply checks if the agent's response has tool calls and routes accordingly.\n \"\"\"\n last_msg = state.get(\"messages\", [])[-1] if state.get(\"messages\") else None\n if not last_msg:\n return \"back\"\n\n tool_calls = getattr(last_msg, \"tool_calls\", None)\n return \"continue\" if tool_calls else \"back\"\n```\n\n**Important Implementation Notes:**\n\n- **`should_continue` must be a static method**: Use `@staticmethod` decorator\n- **The SDK automatically handles fake tool calls**: When agents answer directly, fake \"back\" tool calls are created\n automatically\n- **No manual fake tool call creation needed**: The system handles this transparently\n\n**Routing Guidelines:**\n\n- **Always implement `should_continue`**: This method controls the conversation flow\n- **Return \"continue\" for tool calls**: When agent generates tool calls, route to tools\n- **Return \"back\" for direct answers**: When agent answers directly, return to coordinator\n- **Use fake tool calls**: The system automatically creates fake \"back\" tool calls for consistency\n- **Test both scenarios**: Ensure your agent works with and without tool calls\n\n### Common Patterns\n\n```python\n# Tool function with proper error handling\n@tool\ndef safe_operation(input_data: str) -> str:\n \"\"\"Perform a safe operation with error handling.\"\"\"\n try:\n # Your logic here\n result = process_data(input_data)\n return f\"Success: {result}\"\n except Exception as e:\n return f\"Error: {str(e)}\"\n\n# Agent with comprehensive tool collection\nclass ComprehensiveAgent(BaseAgent):\n def get_tools(self):\n from .tools import (\n primary_tools,\n utility_tools,\n validation_tools\n )\n return primary_tools + utility_tools + validation_tools\n\n def get_system_prompt(self) -> str:\n return (\n \"You are a comprehensive agent with multiple capabilities. \"\n \"Use the appropriate tools based on the user's request. \"\n \"Always explain your reasoning and show your work.\"\n )\n```\n\n## Development\n\n### Setting up Development Environment\n\n```bash\n# Clone the main repository\ngit clone https://github.com/jonaskahn/cadence.git\ncd cadence\n\n# Install SDK dependencies\ncd sdk\npoetry install\n\n# Run tests\npoetry run pytest\n\n# Format code\npoetry run black src/\npoetry run isort src/\n```\n\n### Testing\n\n```bash\n# Run all tests\npoetry run pytest\n\n# Run with coverage\npoetry run pytest --cov=src/cadence_sdk\n\n# Run specific test categories\npoetry run pytest -m \"unit\"\npoetry run pytest -m \"integration\"\n```\n\n## Contributing\n\nWe welcome contributions! Please see our [Contributing Guide](CONTRIBUTING.md) for details.\n\n### Development Setup\n\n1. Fork the repository\n2. Create a feature branch\n3. Make your changes\n4. Add tests\n5. Submit a pull request\n\n## License\n\nThis project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details.\n\n## Troubleshooting\n\n### Common Issues\n\n1. **Plugin Not Loading**: Ensure `register_plugin()` is called in `__init__.py`\n2. **Import Errors**: Check that `cadence_sdk` is properly installed and imported\n3. **Tool Registration**: Verify tools are decorated with `@tool` and included in the tools list\n4. **Metadata Issues**: Ensure all required fields are provided in `PluginMetadata`\n\n### Debug Tips\n\n```python\n# Enable debug logging\nimport logging\nlogging.basicConfig(level=logging.DEBUG)\n\n# Check plugin registration\nfrom cadence_sdk import discover_plugins\nplugins = discover_plugins()\nprint(f\"Discovered plugins: {[p.name for p in plugins]}\")\n\n# Verify tool decoration\nfrom .tools import my_tool\nprint(f\"Tool type: {type(my_tool)}\")\nprint(f\"Tool name: {getattr(my_tool, 'name', 'No name')}\")\n```\n\n## Support\n\n- **Documentation**: [Read the Docs](https://cadence.readthedocs.io/)\n- **Issues**: [GitHub Issues](https://github.com/jonaskahn/cadence/issues)\n- **Discussions**: [GitHub Discussions](https://github.com/jonaskahn/cadence/discussions)\n\n## Quick Reference\n\n### Essential Imports\n\n```python\nfrom cadence_sdk import BaseAgent, BasePlugin, PluginMetadata, tool, register_plugin\n```\n\n### Required Methods\n\n- **Plugin**: `get_metadata()`, `create_agent()`\n- **Agent**: `get_tools()`, `get_system_prompt()`\n- **Tools**: Use `@tool` decorator\n\n### File Structure\n\n```\nmy_plugin/\n\u251c\u2500\u2500 __init__.py # register_plugin(MyPlugin)\n\u251c\u2500\u2500 plugin.py # BasePlugin implementation\n\u251c\u2500\u2500 agent.py # BaseAgent implementation\n\u2514\u2500\u2500 tools.py # @tool decorated functions\n```\n\n### Environment Variables\n\n```bash\nexport CADENCE_PLUGINS_DIR=\"./plugins\"\nexport CADENCE_DEFAULT_LLM_PROVIDER=openai\nexport CADENCE_OPENAI_API_KEY=your-key\n```\n\n---\n\n**Built with \u2764\ufe0f for the Cadence AI community**\n",
"bugtrack_url": null,
"license": "MIT",
"summary": "Cadence SDK - To building custom AI agent plugins for Cadence AI Framework",
"version": "1.0.7",
"project_urls": {
"Documentation": "https://cadence_sdk.readthedocs.io/",
"Homepage": "https://github.com/jonaskahn/cadence-sdk",
"Repository": "https://github.com/jonaskahn/cadence-sdk.git",
"issues": "https://github.com/jonaskahn/cadence-sdk/issues"
},
"split_keywords": [
"ai",
" agents",
" cadence-ai",
" cadence_sdk",
" langchain",
" langgraph",
" plugins"
],
"urls": [
{
"comment_text": "",
"digests": {
"blake2b_256": "1e19d337a0cc37870d9aae977359e3720de5dcc4620a655d212bf8ec6715c89a",
"md5": "d6673fe62d9646bfbb14de1680ee07d7",
"sha256": "cee817bca87cdce298c2432a55f6c30d7a0dd200c593b94989d62f74e2e211f8"
},
"downloads": -1,
"filename": "cadence_sdk-1.0.7-py3-none-any.whl",
"has_sig": false,
"md5_digest": "d6673fe62d9646bfbb14de1680ee07d7",
"packagetype": "bdist_wheel",
"python_version": "py3",
"requires_python": "<3.14,>=3.13",
"size": 32857,
"upload_time": "2025-09-03T09:24:33",
"upload_time_iso_8601": "2025-09-03T09:24:33.524135Z",
"url": "https://files.pythonhosted.org/packages/1e/19/d337a0cc37870d9aae977359e3720de5dcc4620a655d212bf8ec6715c89a/cadence_sdk-1.0.7-py3-none-any.whl",
"yanked": false,
"yanked_reason": null
},
{
"comment_text": "",
"digests": {
"blake2b_256": "25a6df450a392ec3a7ed3d3094bc220bccf7d95b44a0b30f4d77b4e5dc4b3b00",
"md5": "e4bd4f134b212322df2e12b8e728d863",
"sha256": "cdccdf19b482ddb76506494ed9afab9e12ab20ef3e6abb1c61a6a6574b29787d"
},
"downloads": -1,
"filename": "cadence_sdk-1.0.7.tar.gz",
"has_sig": false,
"md5_digest": "e4bd4f134b212322df2e12b8e728d863",
"packagetype": "sdist",
"python_version": "source",
"requires_python": "<3.14,>=3.13",
"size": 28675,
"upload_time": "2025-09-03T09:24:34",
"upload_time_iso_8601": "2025-09-03T09:24:34.648538Z",
"url": "https://files.pythonhosted.org/packages/25/a6/df450a392ec3a7ed3d3094bc220bccf7d95b44a0b30f4d77b4e5dc4b3b00/cadence_sdk-1.0.7.tar.gz",
"yanked": false,
"yanked_reason": null
}
],
"upload_time": "2025-09-03 09:24:34",
"github": true,
"gitlab": false,
"bitbucket": false,
"codeberg": false,
"github_user": "jonaskahn",
"github_project": "cadence-sdk",
"travis_ci": false,
"coveralls": false,
"github_actions": false,
"lcname": "cadence-sdk"
}