A flexible multi-agent orchestration framework with configurable agent and tool registries
Project description
agents-network
A flexible multi-agent orchestration framework. Agents and tools are discovered through registries, configurable from code, JSON, or any data source.
- Type-dispatch pattern — agents/tools registered via decorators, loaded from JSON, DB, or code
- Semantic matching — optional embedding-based agent selection via Ollama or OpenAI
- Audit logging — tracks selection rates, bias metrics, and human-in-loop approval
- Guardrails — configurable blocklist and bias-awareness prompt on built-in agents
- Orchestrator agent — delegate routing to an LLM agent
- Streamlit UI — dashboard for interactive test-drives
Installation
pip install agents-network
With extras:
pip install "agents-network[ui]" # Streamlit dashboard
pip install "agents-network[all]" # everything
Using uv
uv tool install agents-network
# or
uv add agents-network
Quick start
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"
Config from JSON
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.
Custom type registration
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.
Agent matching
When a task arrives, AgentRegistry scores agents by:
- Tag intersection (3x weight) — agents whose tags overlap task tags
- Description keyword match (1x weight) — words from
agent.descriptionfound in task input
Only 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".
Semantic matching
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
...
Orchestrator agent
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"),
)
Audit & human-in-loop
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
Guardrails
Configure blocked patterns before running ConversationAgent:
from agents_network import set_blocked_patterns
set_blocked_patterns(["ignore all instructions", "role-play"])
CLI
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
Streamlit UI
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
DB-backed registries
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))
Development
# 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
CI/CD
Push to any branch triggers:
ci.yml— lint, type-check, test across Python 3.12–3.14, build package
Push a tag (v*) triggers:
release.yml— publish to PyPI, create GitHub Release with auto-changelog
License
MIT
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 agents_network-0.1.0.tar.gz.
File metadata
- Download URL: agents_network-0.1.0.tar.gz
- Upload date:
- Size: 99.6 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
3ad0679f0379e229cb954c8866cb44df11cf819bfdab42b7d0f9b27f1867ca8d
|
|
| MD5 |
008d9ee11ca1d31315b1f0c9475e6ecb
|
|
| BLAKE2b-256 |
16a7645c4eaaeafffb44db3a46c43c3dd5af55e414209c7d2d11f415d7d2ccc9
|
Provenance
The following attestation bundles were made for agents_network-0.1.0.tar.gz:
Publisher:
release.yml on codewithwest/project_agents-network
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
agents_network-0.1.0.tar.gz -
Subject digest:
3ad0679f0379e229cb954c8866cb44df11cf819bfdab42b7d0f9b27f1867ca8d - Sigstore transparency entry: 2097825746
- Sigstore integration time:
-
Permalink:
codewithwest/project_agents-network@43e7c4a2f9ac839eb30ef136022cd9d27a3b7489 -
Branch / Tag:
refs/tags/v0.1.0 - Owner: https://github.com/codewithwest
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@43e7c4a2f9ac839eb30ef136022cd9d27a3b7489 -
Trigger Event:
push
-
Statement type:
File details
Details for the file agents_network-0.1.0-py3-none-any.whl.
File metadata
- Download URL: agents_network-0.1.0-py3-none-any.whl
- Upload date:
- Size: 15.7 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
614c564a471f64bf7fc176c634f54ecee7ff8434330fbd790253f9f3fc0e86fc
|
|
| MD5 |
9b8945d2386459bc949304e357a0a473
|
|
| BLAKE2b-256 |
edaf67e33b2b3c289858e2d53cd26cd7cecf43a6d546836e393b959da9859084
|
Provenance
The following attestation bundles were made for agents_network-0.1.0-py3-none-any.whl:
Publisher:
release.yml on codewithwest/project_agents-network
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
agents_network-0.1.0-py3-none-any.whl -
Subject digest:
614c564a471f64bf7fc176c634f54ecee7ff8434330fbd790253f9f3fc0e86fc - Sigstore transparency entry: 2097825801
- Sigstore integration time:
-
Permalink:
codewithwest/project_agents-network@43e7c4a2f9ac839eb30ef136022cd9d27a3b7489 -
Branch / Tag:
refs/tags/v0.1.0 - Owner: https://github.com/codewithwest
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@43e7c4a2f9ac839eb30ef136022cd9d27a3b7489 -
Trigger Event:
push
-
Statement type: