Skip to main content

Async-first framework for tool-using agents with optional multi-agent delegation.

Project description

Delegent – Async‑First Agentic Framework 🚀

License: Apache-2.0 Python Versions

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 delegent package.

📦 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

All documentation is rendered on the GitHub repository and is also displayed on PyPI via the long_description field.


🤝 Contributing

We welcome contributions! Please:

  1. Fork the repository.
  2. Create a feature branch (git checkout -b my‑feature).
  3. Run the test suite locally (uv run python -m pytest).
  4. 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


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


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_id explicitly into memory calls.
  • Legacy store behavior without explicit session_id remains supported for compatibility.

For parallel calls on the same Agent instance:

  • Agent.run is asyncio non-blocking and can run concurrently.
  • Session-scoped memory isolation depends on passing distinct session_id values in context.
  • Blocking tool handlers can still block the event loop; tools should be async-safe.

LLM Clients

Built‑in:

  • OllamaClient
  • OpenAIClient
  • AnthropicClient
  • GeminiClient

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


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

delegent-0.1.1.tar.gz (63.4 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

delegent-0.1.1-py3-none-any.whl (69.3 kB view details)

Uploaded Python 3

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

Hashes for delegent-0.1.1.tar.gz
Algorithm Hash digest
SHA256 98102ad382431b976946c9d5870e62197dc6d0268e05789622c623c6e77be6f5
MD5 e97c8dfe31e52f8504ad50994caae2e4
BLAKE2b-256 3fa6982814c35c4dbd974d1fe859bf79ba748fed849234e3bb08dc11ce99b563

See more details on using hashes here.

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

Hashes for delegent-0.1.1-py3-none-any.whl
Algorithm Hash digest
SHA256 7ad049ae4dbf78156afc9770f30211d062d00bed0abf0a3738a45a2458ed569f
MD5 0364e183dd1ed6e901495a9164f51a39
BLAKE2b-256 4abba7b9eb53a81fe877ec7bb56ea4af7a550bf3cb4bd8b7575bacd07f01fc58

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page