Modular Agentic Framework — LLM + Agent + Tools with MCP support
Project description
Fluxibly
Modular Agentic Framework — LLM + Agent + Tools
Fluxibly is a framework for building any Agent pipeline simply. Provide general, easy-to-plug, mix-and-match components — to build a new Agent, you only tweak a few core pieces.
Architecture
┌─────────────────────────────────────────────────────────────────────┐
│ USER / APPLICATION │
│ │
│ from fluxibly import Runner │
│ result = await Runner.run("config/agent.yaml", messages, context) │
│ │
└──────────────────────────────┬──────────────────────────────────────┘
│
┌──────────────────────────────▼──────────────────────────────────────┐
│ RUNNER (Execution Loop) │
│ Manages: handoff chain, max_turns, agent swapping, session sandbox │
│ run() → agent.forward() → if handoff → swap agent → repeat │
├─────────────────────────────────────────────────────────────────────┤
│ AGENT LAYER │
│ │
│ BaseAgent (abstract) → SimpleAgent, CustomAgent, ... │
│ Manages: LLMs, tools, prompts, delegation (handoffs + agents) │
│ forward() = prepare → LLM → tool loop → return │
│ │
│ Delegation: │
│ Handoffs ────── transfer_to_xxx → one-way control transfer │
│ Agents-as-tools agent_xxx → delegate & return │
│ Skills ────── load_skill → on-demand instruction loading │
├─────────────────────────────────────────────────────────────────────┤
│ LLM LAYER │
│ │
│ BaseLLM (abstract) → OpenAILLM, AnthropicLLM, GeminiLLM │
│ → LangChainLLM, LiteLLM │
│ prepare() → format for provider │
│ forward() → single inference, standardized output │
├─────────────────────────────────────────────────────────────────────┤
│ TOOLS LAYER │
│ │
│ Functions │ MCP │ Web Search │ File Search │ Shell │ Computer Use │
└─────────────────────────────────────────────────────────────────────┘
Installation
pip install fluxibly
With provider extras:
pip install fluxibly[openai] # OpenAI only
pip install fluxibly[anthropic] # Anthropic only
pip install fluxibly[gemini] # Google Gemini only
pip install fluxibly[all] # All providers
Usage
Run an Agent
The simplest way — pass a config path to Runner.run(). It loads the config, resolves the agent class, and runs.
import asyncio
from fluxibly import Runner
async def main():
result = await Runner.run(
"agents/my_agent.yaml",
[{"role": "user", "content": "Hello!"}],
)
print(result.response.content.output.output_text)
asyncio.run(main())
Runner.run() accepts:
| Input | What happens |
|---|---|
str (path/name) |
Loads config YAML → resolves agent_class → instantiates → runs |
AgentConfig |
Resolves agent_class → instantiates → runs |
BaseAgent instance |
Uses directly → runs |
Agent Config (YAML)
Every agent is a YAML file:
# agents/my_agent.yaml
name: "my_agent"
description: "A helpful general assistant"
# agent_class: "SimpleAgent" # Default — or "path.to.module::CustomAgent"
llm:
model: "gpt-4o"
temperature: 0.7
system_prompt: "You are a helpful assistant."
tools:
- "lookup_faq" # Tool name → resolved from tools/ directory
- "./tools/web_search.yaml" # Tool path → loaded directly
handoffs:
- "billing_specialist" # Agent name → one-way transfer
- agent: "refund_specialist" # With custom options
input_filter: "remove_tools"
agents:
- "researcher" # Agent name → delegate & return
skills:
- "csv-insights" # Skill name → on-demand loading
sandbox: # Optional: sandboxed shell execution config
use_uv: true # Create a uv venv in the sandbox
packages: ["pandas"] # Pre-install packages in the venv
timeout_seconds: 300 # Command timeout (default: 300)
max_tool_iterations: 10
Create a New Tool
Each tool has two files — a YAML schema and an optional Python handler:
tools/get_weather/
├── get_weather.yaml # Required: schema (what the LLM sees)
├── get_weather.py # Optional: handler (what runs when called)
└── requirements.txt # Optional: Python dependencies (auto-installed)
# tools/get_weather/get_weather.yaml — schema
type: "function"
function:
name: "get_weather"
description: "Get current weather for a city"
parameters:
type: "object"
properties:
city: { type: "string", description: "City name" }
required: ["city"]
# tools/get_weather/get_weather.py — handler
async def handler(city: str) -> str:
return f"Weather for {city}: sunny, 72°F"
If a requirements.txt sits alongside the tool YAML, dependencies are auto-installed into the sandbox venv on first use. The file follows the standard uv/pip requirements format:
# tools/get_weather/requirements.txt
requests>=2.31
The handler .py file is auto-discovered by name (same stem as the .yaml). If a tool needs runtime context (e.g., a sandbox session), use the factory pattern:
# tools/file_stats.py — factory handler (receives sandbox session)
async def create_handler(*, session=None, **kwargs):
async def file_stats(file_path: str) -> str:
r = await session.run(f"wc {file_path}")
return r.stdout or r.stderr
return file_stats
Reference tools from an agent config:
# agents/weather_agent.yaml
name: "weather_agent"
llm:
model: "gpt-4o-mini"
system_prompt: "You help with weather queries."
tools:
- "get_weather"
The agent auto-discovers the YAML schema, the .py handler, and requirements.txt — no manual wiring needed.
Create a New Agent (for delegation)
Each agent is its own YAML config. Reference it as a handoff or agent-as-tool from another agent:
# agents/researcher.yaml
name: "researcher"
description: "Do deep web research on a topic. Returns a summary."
llm:
model: "gpt-4o-mini"
temperature: 0.3
system_prompt: "You are a research assistant."
tools:
- "web_search"
# agents/manager.yaml
name: "manager"
description: "Senior analyst who delegates research and writing"
llm:
model: "gpt-4o"
system_prompt: "Break down questions. Use your research and writing agents."
# These become agent_researcher and agent_writer tools
agents:
- "researcher"
- "writer"
result = await Runner.run("agents/manager.yaml", messages)
Create a New Skill
Skills are on-demand instructions that load only when needed. Each skill is a directory with a SKILL.md:
skills/csv-insights/
├── SKILL.md # Required: metadata + instructions
├── requirements.txt # Optional: Python dependencies (auto-installed)
├── scripts/ # Optional: executable code
│ └── analyze.py
└── assets/ # Optional: reference files
---
name: csv-insights
description: Summarize a CSV, compute basic stats, and produce a markdown report.
---
# CSV Insights Skill
## When to use this
- User provides a CSV and wants a summary, stats, or visualization
## How to run
python scripts/analyze.py --input <csv_path> --outdir output
Only the frontmatter (name + description) is loaded at startup. The full body loads when the LLM calls load_skill.
Skill staging: When a sandbox is configured, the skill directory (including scripts/) is automatically copied into the sandbox so the LLM can reference and execute scripts via shell commands.
Auto-install: If the skill directory contains a requirements.txt, dependencies are automatically installed into the sandbox venv before the first LLM call. The file uses standard uv/pip requirements format.
Reference from an agent config:
skills:
- "csv-insights"
Runtime Context
Pass additional resources at runtime via context:
result = await Runner.run(
"agents/my_agent.yaml",
messages,
context={
"prompt_params": {
"domain": "finance",
"system": {"personality": "formal"},
},
"tools": ["extra_tool"],
"agents": ["extra_agent"],
"skills": ["extra_skill"],
},
)
Dynamic Resource Bundles
Inject full resource definitions at runtime (e.g. from an external service):
agent_bundle = {
"name": "specialist_v2",
"type": "agent",
"resources": {
"config.yaml": 'name: "specialist_v2"\nllm:\n model: "gpt-4o"\n...',
"agent.py": 'class SpecialistV2(SimpleAgent):\n ...',
},
"structure": {"config.yaml": "file", "agent.py": "file"},
}
result = await Runner.run(
"agents/triage.yaml",
messages,
context={"agents": [agent_bundle]},
)
Bundles are materialized to the session sandbox as real files, then resolved identically to any other path.
Sandboxed Execution
When tools or skills need to run shell commands, Fluxibly provides OS-level isolation using Anthropic's sandbox-runtime (SRT) and fast Python environment creation via uv.
Sandboxing is lazy — nothing is created until the first shell command runs. The sandbox is then reused for the entire session and cleaned up automatically by the Runner.
Install SRT for OS-level isolation (optional):
npm install -g @anthropic-ai/sandbox-runtime
brew install ripgrep # Required by SRT (macOS)
If SRT is not installed, commands fall back to direct subprocess execution with a warning.
Sandbox YAML config
sandbox:
use_uv: true # Create a uv venv in the sandbox (default: true)
python_version: "3.11" # Python version for uv venv (default: system)
packages: ["pandas", "matplotlib"] # Pre-install packages on first use (prefer requirements.txt)
timeout_seconds: 300 # Max seconds per command (default: 300)
allowed_domains: # Network allowlist (default: pypi.org, github.com)
- "pypi.org"
- "files.pythonhosted.org"
deny_read: # Filesystem read denylist (default: ~/.ssh, ~/.aws, .env)
- "~/.ssh"
- "~/.aws"
allow_write: ["/data/output"] # Extra writable paths beyond sandbox_dir
Prefer
requirements.txt: Instead of listing packages in the sandbox config, place arequirements.txtin each tool or skill directory. Dependencies are auto-installed on first use, and each tool/skill owns its own deps.
Programmatic usage
from fluxibly import SandboxSession
from fluxibly.tools import ToolService
session = SandboxSession("/tmp/my-sandbox")
ts = ToolService()
ts.register_sandboxed_shell(session) # Registers a "shell" function tool
See fluxibly/tools/sandbox.py and examples/03_multi_agent.py for details.
Environment Setup
Copy .env.example to .env and fill in your values:
cp .env.example .env
The .env file is auto-loaded when you import fluxibly. At minimum, set your LLM provider API key:
# .env
OPENAI_API_KEY=sk-...
# or
ANTHROPIC_API_KEY=sk-ant-...
# or
GOOGLE_API_KEY=...
See .env.example for the full list of available settings (database, monitoring, etc.).
Monitoring
Fluxibly includes built-in monitoring that records traces, spans, and tool calls to a PostgreSQL database. Monitoring is configured exclusively via environment variables in your .env file — never through agent YAML configs.
1. Set up the database
Create a PostgreSQL database for monitoring data:
createdb fluxibly_monitoring
2. Enable monitoring in .env
FLUXIBLY_MONITORING_ENABLED=true
FLUXIBLY_MONITORING_DASHBOARD_DB_HOST=localhost
FLUXIBLY_MONITORING_DASHBOARD_DB_PORT=5432
FLUXIBLY_MONITORING_DASHBOARD_DB_NAME=fluxibly_monitoring
FLUXIBLY_MONITORING_DASHBOARD_DB_USER=postgres
FLUXIBLY_MONITORING_DASHBOARD_DB_PASSWORD=your_password
When FLUXIBLY_MONITORING_ENABLED=true, all agents automatically pick up monitoring — no code changes needed. Tables are created automatically on first run.
3. Launch the monitoring dashboard
python -m fluxibly.monitoring.dashboard
The dashboard runs at http://localhost:8555 by default. You can customize the host/port:
python -m fluxibly.monitoring.dashboard --host 0.0.0.0 --port 9000
Or via environment variables:
FLUXIBLY_MONITORING_DASHBOARD_HOST=0.0.0.0
FLUXIBLY_MONITORING_DASHBOARD_PORT=8555
Custom Agents
Extend AgentTemplate to build custom agents with pre/post hooks:
from fluxibly.agent import AgentTemplate, AgentConfig, AgentResponse
class MyAgent(AgentTemplate):
async def pre_forward(self, messages, context):
# Inject custom params before the pipeline runs
context.setdefault("prompt_params", {})
context["prompt_params"]["custom_var"] = "value"
return messages, context
async def post_forward(self, response, messages, context):
# Inspect or modify the response
return response
See examples/04_custom_agent.py for a full working example.
Requirements
- Python 3.11+
- API keys for your chosen LLM provider
- For sandboxed execution (optional):
License
MIT License — see LICENSE for details.
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file fluxibly-1.0.0.tar.gz.
File metadata
- Download URL: fluxibly-1.0.0.tar.gz
- Upload date:
- Size: 361.9 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.11.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
6cbf8d22ca501702d3162114031d0bf688e9ae886df5972337d4db7ebf522f21
|
|
| MD5 |
9ab6a9a1b0b159fbe0cfecc760f77b62
|
|
| BLAKE2b-256 |
bbc9332a979ca7d10dd6b5f5f24bba676825548c6e929a9bf04697fa7d85a3ea
|
File details
Details for the file fluxibly-1.0.0-py3-none-any.whl.
File metadata
- Download URL: fluxibly-1.0.0-py3-none-any.whl
- Upload date:
- Size: 133.6 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.11.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
8e2c5bad7a783aa578b38d4628e02bc29aa0dd361c25c2c31c3d1cbe422620c6
|
|
| MD5 |
595e8d63d60cecb9f5838b2b47a5b9f8
|
|
| BLAKE2b-256 |
ea283ac0df5b379a2dab8499310d4b54ee8652fff1e50be6ff51349e45654b3a
|