Async-first framework for tool-using agents with optional multi-agent delegation.
Project description
Delegent – Async‑First Agentic Framework 🚀
Delegent is a lightweight, async‑first framework for building tool‑using agents. It supports:
- Simple single‑step agents (
SimpleAgentBuilder) and advanced multi‑agent delegation (AgentBuilder). - Seamless integration with any LLM client (Ollama, OpenAI, Anthropic, Gemini).
- Structured “thinking” streams, observability hooks and metrics.
- Built‑in support for Nuncio multi‑agent delegation, MCP remote tools, and approval‑required tool calls.
- A clean public API that can be imported directly from the top‑level
delegentpackage.
📦 Installation
Install the official package via pip:
pip install delegent
Or using uv:
uv pip install delegent
✨ Quick Start – Simple Runtime
import asyncio
from delegent import SimpleAgentBuilder, tool, OllamaClient
@tool(
name="local.greet",
description="Say hello",
args_schema={"type": "object", "properties": {"name": {"type": "string"}}},
keywords=["greet", "hello", "welcome"],
)
async def greet(args):
return f"hello, {args['name']}"
async def main():
llm = OllamaClient(model="llama3.1")
agent = (
SimpleAgentBuilder()
.system("You are a helpful planner.")
.llm(llm, format="json")
.tools([greet])
.build()
)
result = await agent.run("greet the user", {"name": "world"})
print(result)
if __name__ == "__main__":
asyncio.run(main())
🧭 Streaming "Thinking" – Structured Events
from delegent import AgentBuilder, OllamaClient, Event
class PrintHandler:
async def handle(self, event: Event):
print(event.type, event.data)
agent = (
AgentBuilder()
.system("You are a planner.")
.llm(OllamaClient(model="llama3.1"), format="json")
.stream(PrintHandler())
.build()
)
Available event types include planner.started, tool.call, tool.result, final, and, when delegating, delegate.call, delegate.result, etc. Use stream_mode("thinking") for a user‑friendly summary or stream_mode("both") for raw + thinking.
🔧 Advanced Features
Multi‑Agent Delegation (Nuncio)
from delegent import AgentBuilder, OllamaClient
agent = (
AgentBuilder()
.system("You are a Trading Agent.")
.llm(OllamaClient(model="llama3.1"), format="json")
.multi_agent(hub_uri="ws://localhost:8080/nuncio", token="dev-token", role="TRADING_AGENT")
.delegate_capabilities(["RESEARCH_AGENT"]) # expose sub‑role(s)
.build()
)
await agent.connect()
Tool Approval (e.g., Payments)
from delegent import AgentBuilder, tool, ApprovalHandler
@tool(
name="payments.charge",
description="Charge a customer",
args_schema={"type": "object", "properties": {"amount": {"type": "number"}}},
requires_approval=True,
approval_prompt="Charge the customer?",
cost_estimate="$100",
)
async def charge(args):
return {"charged": True, "amount": args["amount"]}
class MyApproval(ApprovalHandler):
async def approve(self, tool, args):
# Hook into your UI / consent flow
return True
agent = AgentBuilder().approval(MyApproval()).tools([charge]).build()
MCP Remote Tools
from delegent import AgentBuilder, MCPClientConfig, MCPTool
async def call_tool(name, args):
# Call your remote service here
return {"ok": True}
config = MCPClientConfig(
tools=[MCPTool(name="mcp.search", description="Search", args_schema={"type": "object"})],
call_tool=call_tool,
)
agent = AgentBuilder().mcp(config).llm(OllamaClient(model="llama3.1"), format="json").build()
📚 Documentation
- Specification –
docs/FRAMEWORK.md - Full User Guide –
docs/USER_GUIDE.md - Cookbook (Examples) –
docs/COOKBOOK.md
All documentation is rendered on the GitHub repository and is also displayed on PyPI via the long_description field.
🤝 Contributing
We welcome contributions! Please:
- Fork the repository.
- Create a feature branch (
git checkout -b my‑feature). - Run the test suite locally (
uv run python -m pytest). - Submit a Pull Request.
For detailed guidelines, see CONTRIBUTING.md.
📄 License
Released under the Apache License 2.0. See the bundled LICENSE file for details.
📬 Contact & Support
- GitHub Issues: https://github.com/nuncio-labs/delegent/issues
- Email:
nunciolabs@gmail.com
Enjoy building next‑generation agents with Delegent!
This framework is intentionally simple by default and modular:
- Create agents with a system prompt
- Attach local tools easily
- Mirror MCP tools into the same registry
- Stream structured “thinking” events
- Use any LLM client (Ollama, OpenAI, Anthropic, Gemini)
- Use Nuncio for multi‑agent delegation (with streaming)
Simple runtime uses SimpleAgentBuilder and only supports action + final plans.
Advanced features (delegate/approval) use AgentBuilder.
Documentation Map
- Specification:
docs/FRAMEWORK.md - Full Guide:
docs/USER_GUIDE.md - Practical Examples (Cookbook):
docs/COOKBOOK.md
Quick Start (Simple Runtime)
import asyncio
from delegent import SimpleAgentBuilder, tool, OllamaClient
@tool(
name="local.greet",
description="Say hello",
args_schema={"type": "object", "properties": {"name": {"type": "string"}}},
keywords=["greet", "hello", "welcome"],
)
async def greet(args):
return f"hello, {args['name']}"
async def main():
ollama = OllamaClient(model="llama3.1")
agent = (
SimpleAgentBuilder()
.system("You are a helpful planner.")
.llm(ollama, format="json")
.tools([greet])
.build()
)
result = await agent.run("greet the user", {"name": "world"})
print(result)
if __name__ == "__main__":
asyncio.run(main())
Streaming “Thinking” (Structured Events)
from delegent import AgentBuilder, OllamaClient, Event
class PrintHandler:
async def handle(self, event: Event):
print(event.type, event.data)
agent = (
SimpleAgentBuilder()
.system("You are a planner.")
.llm(OllamaClient(model="llama3.1"), format="json")
.stream(PrintHandler())
.build()
)
Events emitted:
planner.started, planner.plan, tool.call, tool.result, final
In delegate mode, you also get:
peer.dialog.started, peer.dialog.ended, tool.stream, delegate.call, delegate.result
New lifecycle/correlation events include:
run.started, planner.call, planner.error
All runtime events include run_id (auto-generated if absent). If provided in context, session_id is propagated in event payloads.
Stream Modes
Stream output can be selected with:
raw(default): existing technical events unchanged.thinking: user-friendly thinking summaries.both: thinking summaries plus prefixed raw events (raw.<event_type>).
agent = (
AgentBuilder()
.system("You are a planner.")
.llm(OllamaClient(model="llama3.1"), format="json")
.stream_mode("thinking")
.build()
)
You can also attach a custom thinking sink:
class MyThinkingHandler:
async def handle(self, event):
print("THINK:", event.type, event.data)
agent = AgentBuilder().stream_mode("thinking").thinking_handler(MyThinkingHandler()).build()
Thinking mode currently includes planner thought text and links parent run_id with delegated child run IDs when available.
Friendly stream is optimized for readability; raw stream remains the source of truth for debugging.
Delegation is not a tool. The planner should return a delegate plan:
{
"type": "delegate",
"thought": "Ask the research agent for a signal.",
"agent_role": "RESEARCH_AGENT",
"query": "Provide a BUY/NO_BUY signal for AAPL",
"context": { "symbol": "AAPL" }
}
Preferred Tools (Soft Bias)
The planner prefers tools when relevant. You can also override explicitly:
result = await agent.run(
"greet the user",
{"name": "world", "preferred_tools": ["local.greet"]},
)
Planning History
The planner receives step history and a summary (when history is long).
agent = AgentBuilder().history_limit(5).build()
Planner context includes:
history: recent steps (plan + results)history_summary: compact summary of older steps
Observability (Opt-In)
Metrics
from delegent import AgentBuilder, FrameworkMetricsCollector
collector = FrameworkMetricsCollector()
agent = AgentBuilder().llm(ollama, format="json").metrics(collector).build()
Structured Logging (JSON stdout)
agent = AgentBuilder().llm(ollama, format="json").logging().build()
Or provide config:
agent = AgentBuilder().logging({"level": "INFO", "redact_keys": ["email"]}).build()
Internal Tracing (In-Memory Spans)
from delegent import AgentBuilder, TraceCollector
trace_collector = TraceCollector()
agent = AgentBuilder().llm(ollama, format="json").tracing(trace_collector).build()
No observability handler is auto-enabled by default.
Tool Approval (Payments / Sensitive Actions)
Mark tools as requiring approval and provide a handler:
from delegent import AgentBuilder, tool, ApprovalHandler
@tool(
name="payments.charge",
description="Charge a customer",
args_schema={"type": "object", "properties": {"amount": {"type": "number"}}},
requires_approval=True,
approval_prompt="Charge the customer?",
cost_estimate="$100",
)
async def charge(args):
return {"charged": True, "amount": args["amount"]}
class MyApproval(ApprovalHandler):
async def approve(self, tool, args):
return True # integrate your UI/consent flow here
agent = AgentBuilder().approval(MyApproval()).tools([charge]).build()
MCP Tools (Registry‑Backed)
from delegent import AgentBuilder, MCPClientConfig, MCPTool
async def call_tool(name, args):
# call your MCP server here
return {"ok": True}
config = MCPClientConfig(
tools=[MCPTool(name="mcp.search", description="Search", args_schema={"type": "object"})],
call_tool=call_tool,
)
agent = AgentBuilder().mcp(config).llm(ollama, format="json").build()
Nuncio Multi‑Agent (Delegate Mode)
from delegent import AgentBuilder, OllamaClient
agent = (
AgentBuilder()
.system("You are a Trading Agent.")
.llm(OllamaClient(model="llama3.1"), format="json")
.multi_agent(hub_uri="ws://localhost:8080/nuncio", token="dev-token", role="TRADING_AGENT")
.delegate_capabilities(["RESEARCH_AGENT"])
.build()
)
await agent.connect()
Memory and Sessions
- Memory APIs support explicit session scoping:
add(text, metadata=None, session_id=None)retrieve(query, k=5, session_id=None)
- The agent passes
context.session_idexplicitly into memory calls. - Legacy store behavior without explicit
session_idremains supported for compatibility.
For parallel calls on the same Agent instance:
Agent.runis asyncio non-blocking and can run concurrently.- Session-scoped memory isolation depends on passing distinct
session_idvalues in context. - Blocking tool handlers can still block the event loop; tools should be async-safe.
LLM Clients
Built‑in:
OllamaClientOpenAIClientAnthropicClientGeminiClient
All implement: complete(prompt, **kwargs)
Public API
-
SimpleAgentBuilder(simple runtime).system(prompt).llm(client, **kwargs).tools([...]).mcp(config).stream(handler).build() -> Agent
-
AgentBuilder.system(prompt).llm(client, **kwargs).tools([...]).mcp(config).nuncio(config)(legacy remote tool adapter).remote_tool(name, role)(legacy).multi_agent(hub_uri, token, role, capabilities).delegate_capabilities(roles).stream(handler).stream_mode("raw" | "thinking" | "both").thinking_handler(handler).metrics(collector).logging(handler_or_config=None).tracing(handler_or_collector=None).allow_delegate(enabled).delegate_timeout(seconds).build() -> Agent
-
Agent.run(query, context=None).stream(query, context=None)→ async generator of events.connect()/.close()for delegate mode
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 delegent-0.1.1.tar.gz.
File metadata
- Download URL: delegent-0.1.1.tar.gz
- Upload date:
- Size: 63.4 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: uv/0.8.4
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
98102ad382431b976946c9d5870e62197dc6d0268e05789622c623c6e77be6f5
|
|
| MD5 |
e97c8dfe31e52f8504ad50994caae2e4
|
|
| BLAKE2b-256 |
3fa6982814c35c4dbd974d1fe859bf79ba748fed849234e3bb08dc11ce99b563
|
File details
Details for the file delegent-0.1.1-py3-none-any.whl.
File metadata
- Download URL: delegent-0.1.1-py3-none-any.whl
- Upload date:
- Size: 69.3 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: uv/0.8.4
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
7ad049ae4dbf78156afc9770f30211d062d00bed0abf0a3738a45a2458ed569f
|
|
| MD5 |
0364e183dd1ed6e901495a9164f51a39
|
|
| BLAKE2b-256 |
4abba7b9eb53a81fe877ec7bb56ea4af7a550bf3cb4bd8b7575bacd07f01fc58
|