Observed since 20 Oct, 23:27 UTC
No superseded metadata states collected. Unchanged observations do not create new revisions.
A flexible multi-agent orchestration framework with configurable agent and tool registries
A flexible multi-agent orchestration framework. Agents and tools are discovered through registries, configurable from code, JSON, or any data source.
pip install agents-network
With extras:
pip install "agents-network[ui]" # Streamlit dashboard
pip install "agents-network[all]" # everything
uv tool install agents-network
# or
uv add agents-network
Define a tool and an agent, register them, and let the network route tasks:
import asyncio
from agents_network import Tool, Agent, Task, Message, ToolRegistry, AgentRegistry, AgentNetwork
from agents_network import tool_type, agent_type
@tool_type()
class UppercaseTool(Tool):
async def run(self, input: dict) -> str:
return input.get("text", "").upper()
@agent_type()
class TransformerAgent(Agent):
async def run(self, task: Task, tool_registry: ToolRegistry) -> Message:
tools = self.select_tools(task, tool_registry)
result = await tools[0].run({"text": task.input})
return Message(role="assistant", content=result)
tool_registry = ToolRegistry([UppercaseTool(name="upper", description="converts text to uppercase")])
agent_registry = AgentRegistry([
TransformerAgent(name="transformer", description="transforms text", tool_names=["upper"]),
])
network = AgentNetwork(agent_registry=agent_registry, tool_registry=tool_registry)
result = asyncio.run(network.run(Task(input="hello world")))
print(result.content) # "HELLO WORLD"
Define agents and tools in a JSON file and load at runtime:
{
"tools": [
{
"type": "calculator",
"name": "calc",
"description": "Performs arithmetic",
"precision": 5
}
],
"agents": [
{
"type": "math_agent",
"name": "math-wizard",
"description": "Solves math problems",
"tool_names": ["calc"]
}
]
}
network = AgentNetwork.from_json("config.json")
result = asyncio.run(network.run(Task(input="2 + 2")))
Each tool and agent references a registered Python class via the type field.
Extra fields in the JSON are merged into config automatically.
The plugins list (or --load flag) imports Python modules that register custom types via @tool_type() / @agent_type() decorators.
{
"plugins": ["examples.types"],
"tools": [...],
"agents": [...]
}
Only import modules you trust — --load and plugins use importlib.import_module() and load arbitrary Python code.
import ast
import operator
@tool_type("calculator")
class Calculator(Tool):
precision: int = 2
_OPS = {
ast.Add: operator.add,
ast.Sub: operator.sub,
ast.Mult: operator.mul,
ast.Div: operator.truediv,
}
def _eval(self, node: ast.AST) -> float:
if isinstance(node, ast.Expression):
return self._eval(node.body)
if isinstance(node, ast.Constant) and isinstance(node.value, (int, float)):
return float(node.value)
if isinstance(node, ast.BinOp) and type(node.op) in self._OPS:
return self._OPS[type(node.op)](self._eval(node.left), self._eval(node.right))
if isinstance(node, ast.UnaryOp) and isinstance(node.op, ast.USub):
return -self._eval(node.operand)
raise ValueError("Unsupported expression")
async def run(self, input: dict) -> str:
tree = ast.parse(input["expression"], mode="eval")
return str(round(self._eval(tree), self.precision))
The name passed to @tool_type("calculator") must match the "type" field in JSON.
When a task arrives, AgentRegistry scores agents by:
agent.description found in task inputOnly agents scoring at or above min_score (default: 1) are returned. If no agents meet the threshold, an empty list is returned and the network reports "No suitable agent found".
Pass an EmbeddingMatcher to AgentRegistry for embedding-based scoring:
from agents_network import AgentRegistry, EmbeddingMatcher
matcher = EmbeddingMatcher(model="nomic-embed-text-v2-moe")
registry = AgentRegistry(agents=[...], matcher=matcher)
When a matcher is configured, AgentNetwork.run() automatically uses match_semantic().
Override for custom routing:
class CustomRegistry(AgentRegistry):
def match(self, task: Task) -> list[Agent]:
# your routing logic
...
Optionally give the network an orchestrator agent that picks the best match:
@agent_type()
class Router(Agent):
async def run(self, task: Task, tool_registry: ToolRegistry) -> Message:
return Message(role="assistant", content="math-wizard")
network = AgentNetwork(
agent_registry=agent_registry,
tool_registry=tool_registry,
orchestrator=Router(name="router", description="routes tasks"),
)
from agents_network import AuditLog, AgentNetwork
audit = AuditLog()
async def approve(task):
# Manual review logic
return True
network = AgentNetwork(
agent_registry=agent_registry,
tool_registry=tool_registry,
audit_log=audit,
approval_callback=approve,
)
result = await network.run(task)
print(network.bias_report()) # selection rates per agent
Configure blocked patterns before running ConversationAgent:
from agents_network import set_blocked_patterns
set_blocked_patterns(["ignore all instructions", "role-play"])
agents-network "analyse this data" --config config.json
| Argument | Default | Description |
|---|---|---|
input (positional) | — | Task input text |
--config / -c | (required) | Path to JSON config |
--interactive / -i | false | Interactive REPL mode |
--load / -l | — | Python module to import for custom types (repeatable) |
--tags / -t | [] | Tags to attach to the task |
Interactive mode:
agents-network --config config.json --interactive
pip install "agents-network[ui]"
streamlit run agents_network/ui.py
Custom types load via AGENTS_NETWORK_LOAD env var:
AGENTS_NETWORK_LOAD=my_custom_agents streamlit run agents_network/ui.py
Subclass ToolRegistry or AgentRegistry and override:
class PostgresAgentRegistry(AgentRegistry):
def __init__(self, dsn: str):
super().__init__()
rows = query_db(dsn, "SELECT name, description, type, config FROM agents")
for row in rows:
self.register(Agent.from_dict(row))
# Install dev dependencies
uv sync --group dev
# or
pip install "agents-network[dev]"
# Lint
uv run ruff check agents_network/
# Type check
uv run mypy agents_network/
# Test
uv run pytest
Push to any branch triggers:
ci.yml — lint, type-check, test across Python 3.12–3.14, build packagePush a tag (v*) triggers:
release.yml — publish to PyPI, create GitHub Release with auto-changelogMIT
Download counts and popularity will appear here as the collector receives them.
| Version | Published | Direct dependencies | Open version |
|---|---|---|---|
| 0.1.0 | 7 Jul 2026 | 7 |
This describes our collected data; it is not a security assessment.
Observed since 20 Oct, 23:27 UTC
No superseded metadata states collected. Unchanged observations do not create new revisions.
package:5e49befbad5d23e4d03c6c7ee9ea1001b02c050381553ee3369978b89b687f62