Open Source Python Library

Deploy AI Agents
with One Codebase

Framework-agnostic library for deploying AI agents via HTTP, A2A, and MCP — with built-in observability, prompt management, evaluation, and agent discovery through KiboStudio.

uv add "kiboup[all]"

Three Protocols, One API

Choose the right protocol for your use case. All share authentication, logging, and Studio integration.

HTTP

KiboAgentApp

Standard REST API with streaming (SSE), WebSocket, task tracking, and health checks. Works with any frontend.

Web AppsREST APIsMicroservices
POST /invocations GET /ping WS /ws
MCP

KiboAgentMcp

Expose agent capabilities as discoverable tools via the Model Context Protocol. Compatible with MCP Inspector and IDEs.

IDE IntegrationsTool AgentsPlugins
@app.tool() @app.resource() @app.prompt()
A2A

KiboAgentA2A

Google's Agent-to-Agent protocol with auto-generated Agent Cards, skill-based routing, and task lifecycle.

Multi-AgentAgent DiscoveryCross-Org
/.well-known/agent.json @app.executor
Built-in Developer Console

KiboStudio

Full observability, prompt management, evaluation, and agent discovery in a single web interface.

๐Ÿ“ก

Agent Discovery

Service registry where agents register, send heartbeats, and discover each other at runtime. Monitor health, uptime, memory, and capabilities from the Dashboard.

๐Ÿ“Š

Traces & Observability

Automatic trace reporting with full span hierarchy: invocation, agent_run, llm_call, tool_call, retrieval. View input/output data, LLM token usage, duration, and errors.

๐Ÿ”€

Graph Visualization

ADK-style visual graph rendering of trace span hierarchies. Agent nodes as green ellipses, tool/LLM nodes as boxes, with bezier curve edges and arrowheads.

๐Ÿ’ฌ

Chat Interface

Built-in chat to test any registered agent directly from the browser. Full markdown rendering, message history, and real-time responses.

๐Ÿšฉ

Feature Flags & Params

Control agent behavior at runtime without redeploying. Toggle capabilities on/off with feature flags or change configuration via dynamic parameters. Global and per-agent scopes.

๐Ÿ“

Prompt Management

Version-controlled prompt templates with variable extraction, active version selection, and model configuration. Fetch prompts at runtime via the SDK.

๐Ÿงช

Evaluation (LLM-as-Judge)

Automated quality evaluation using GPT-4o-mini. Scores traces on answer relevancy, coherence, completeness, and harmfulness (0.0-1.0).

๐Ÿ”Œ

StudioClient SDK

Full async Python SDK for agents. Auto-registration, heartbeats, flag/param caching (30s TTL), and inline integration with KiboAgentClient and KiboMcpClient.

๐Ÿ—„๏ธ

SQLite Backend

Zero-config persistence with SQLite WAL mode. No external databases required. Just point to a file path and go.

Launch KiboStudio in 3 lines

from kiboup.studio import KiboStudio

studio = KiboStudio(db_path="kibostudio.db", debug=True)
studio.run(host="0.0.0.0", port=8000)

Examples

Ready-to-run examples covering every protocol and feature.

Server
from kiboup import KiboAgentApp, LLMUsage
from langchain_openai import ChatOpenAI
from langgraph.graph import START, MessagesState, StateGraph

app = KiboAgentApp(
    api_keys={"sk-frontend-abc": "web-app"}
)

llm = ChatOpenAI(model="gpt-4o-mini")
builder = StateGraph(MessagesState)

def chatbot(state):
    return {"messages": [llm.invoke(state["messages"])]}

builder.add_node("chatbot", chatbot)
builder.add_edge(START, "chatbot")
graph = builder.compile()

@app.entrypoint
async def invoke(payload, context):
    result = await graph.ainvoke(
        {"messages": [{"role": "user", "content": payload["prompt"]}]}
    )
    return {"response": result["messages"][-1].content}

app.run(port=8080)
Client
import asyncio
from kiboup import KiboAgentClient

async def main():
    async with KiboAgentClient(
        base_url="http://127.0.0.1:8080",
        api_key="sk-frontend-abc",
    ) as client:
        health = await client.ping()
        result = await client.invoke(
            {"prompt": "What is AI?"}
        )
        print(result["response"])

asyncio.run(main())
cURL
curl -X POST http://127.0.0.1:8080/invocations \
  -H "Content-Type: application/json" \
  -H "X-API-Key: sk-frontend-abc" \
  -d '{"prompt": "What is AI?"}'
Streaming Server (SSE)
from kiboup import KiboAgentApp
from langchain_openai import ChatOpenAI
from langgraph.graph import START, MessagesState, StateGraph

app = KiboAgentApp(api_keys={"sk-chat-abc": "chat"})
llm = ChatOpenAI(model="gpt-4o-mini", streaming=True)

builder = StateGraph(MessagesState)
def chatbot(state):
    return {"messages": [llm.invoke(state["messages"])]}
builder.add_node("chatbot", chatbot)
builder.add_edge(START, "chatbot")
graph = builder.compile()

@app.entrypoint
async def invoke(payload, context):
    messages = payload.get("messages",
        [{"role": "user", "content": payload["prompt"]}])

    async def token_stream():
        async for event in graph.astream_events(
            {"messages": messages}, version="v2"
        ):
            if event.get("event") == "on_chat_model_stream":
                content = event["data"]["chunk"].content
                if content:
                    yield {"token": content}
        yield {"done": True}

    return token_stream()

app.run(port=8080)
Streaming Client
import asyncio, sys
from kiboup import KiboAgentClient

async def chat_loop():
    async with KiboAgentClient(
        "http://localhost:8080",
        api_key="sk-chat-abc"
    ) as client:
        while True:
            user_input = input("You: ")
            sys.stdout.write("AI: ")

            async for chunk in client.stream(
                {"prompt": user_input}
            ):
                token = chunk.get("token")
                if token:
                    sys.stdout.write(token)
                    sys.stdout.flush()
            print("\n")

asyncio.run(chat_loop())
MCP Server
from kiboup import KiboAgentMcp

app = KiboAgentMcp(
    name="LangGraph MCP Server",
    api_keys={"sk-mcp-abc": "web-app"},
)

@app.tool()
async def ask(question: str) -> str:
    """Ask a question to GPT-4o-mini."""
    result = await graph.ainvoke(
        {"messages": [{"role": "user",
                      "content": question}]}
    )
    return result["messages"][-1].content

@app.tool()
def summarize(text: str) -> str:
    """Summarize text using GPT-4o-mini."""
    result = graph.invoke(
        {"messages": [{"role": "user",
         "content": f"Summarize:\n{text}"}]}
    )
    return result["messages"][-1].content

app.run(transport="sse")
MCP Client
import asyncio
from kiboup import KiboMcpClient

async def main():
    async with KiboMcpClient(
        "http://localhost:8000/sse",
        api_key="sk-mcp-abc"
    ) as client:
        tools = await client.list_tools()
        print(f"Tools: {tools}")

        result = await client.call_tool(
            "ask",
            {"question": "What is AI?"}
        )
        print(result)

asyncio.run(main())
A2A Server
from kiboup.a2a.server import (
    AgentExecutor, AgentSkill,
    KiboAgentA2A, TaskUpdater
)

app = KiboAgentA2A(
    name="Chat Agent",
    description="GPT-4o-mini via LangGraph",
    api_keys={"sk-a2a-xyz": "client"},
    skills=[AgentSkill(
        id="chat", name="Chat",
        description="Answer questions",
        tags=["chat", "qa"],
        input_modes=["text/plain"],
        output_modes=["text/plain"],
    )],
)

@app.executor
class ChatAgent(AgentExecutor):
    async def execute(self, ctx, queue):
        from a2a.utils import new_agent_text_message
        user_input = ctx.get_user_input()
        result = await graph.ainvoke(
            {"messages": [{"role": "user",
                          "content": user_input}]}
        )
        await queue.enqueue_event(
            new_agent_text_message(
                result["messages"][-1].content
            )
        )

    async def cancel(self, ctx, queue):
        updater = TaskUpdater(queue, ctx.task_id, ctx.context_id)
        await updater.cancel()

app.run()
A2A Client
import asyncio
from kiboup import KiboA2AClient

async def main():
    async with KiboA2AClient(
        "http://localhost:8000",
        api_key="sk-a2a-xyz"
    ) as client:
        card = client.agent_card
        print(f"Agent: {card.name}")
        print(f"Skills: {[s.name for s in card.skills]}")

        response = await client.send(
            "What is the capital of France?"
        )
        print(response)

asyncio.run(main())
Agent Card
// GET /.well-known/agent.json
{
  "name": "Chat Agent",
  "description": "GPT-4o-mini via LangGraph",
  "skills": [{
    "id": "chat",
    "name": "Chat",
    "tags": ["chat", "qa"]
  }]
}
Researcher Agent
from kiboup import KiboAgentApp
from kiboup.studio import StudioClient

app = KiboAgentApp()
studio = StudioClient(
    studio_url="http://127.0.0.1:8000",
    agent_id="researcher",
    agent_name="researcher",
    agent_endpoint="http://127.0.0.1:8081",
    capabilities=["research", "delegate"],
)
app.attach_studio(studio)

@app.entrypoint
async def invoke(payload, context):
    research = await do_research(payload["prompt"])

    # Feature flag: delegate to writer?
    if await studio.is_flag_enabled("delegate_to_writer"):
        # Discover writer agent via Studio
        agents = await studio.list_agents()
        writer = next(
            (a for a in agents
             if a["agent_id"] == "writer"), None
        )
        if writer:
            style = await studio.get_param(
                "writer_style", default="markdown"
            )
            # Delegate to writer endpoint
            result = await call_writer(
                writer["endpoint"], research, style
            )
            return {"response": result, "delegated": True}

    return {"response": research}

app.run(port=8081)
Writer Agent
from kiboup import KiboAgentApp
from kiboup.studio import StudioClient

app = KiboAgentApp()
studio = StudioClient(
    studio_url="http://127.0.0.1:8000",
    agent_id="writer",
    agent_name="writer",
    agent_endpoint="http://127.0.0.1:8082",
    capabilities=["writing", "formatting"],
)
app.attach_studio(studio)

@app.entrypoint
async def invoke(payload, context):
    prompt = payload.get("prompt", "")
    research = payload.get("research", "")
    style = payload.get("style", "markdown")

    content = (
        f"Topic: {prompt}\n"
        f"Research:\n{research}\n"
        f"Format in {style} style."
    )

    result = await graph.ainvoke(
        {"messages": [{"role": "user",
                      "content": content}]}
    )
    return {
        "response": result["messages"][-1].content,
        "agent": "writer",
        "style": style,
    }

app.run(port=8082)
Run It
# Terminal 1: KiboStudio
uv run python examples/studio_example.py

# Terminal 2: Researcher
OPENAI_API_KEY=sk-... uv run python \
  examples/multi_agent_example.py --agent researcher

# Terminal 3: Writer
OPENAI_API_KEY=sk-... uv run python \
  examples/multi_agent_example.py --agent writer
Server
from kiboup import KiboAgentApp

app = KiboAgentApp()

@app.entrypoint
async def invoke(payload, context):
    return {"response": "Hello from mTLS!"}

# Auto-generates CA, server & client certs
app.run(host="0.0.0.0", port=8443, mtls=True)
Client
import asyncio
from kiboup import KiboAgentClient

async def main():
    async with KiboAgentClient(
        base_url="https://localhost:8443",
        mtls=True,
    ) as client:
        result = await client.invoke({"prompt": "Hello!"})

asyncio.run(main())
Custom Certs Directory
# Via environment variable
KIBO_CERTS_DIR=/path/to/certs uv run python my_server.py

# Via MTLSConfig
from kiboup import MTLSConfig

config = MTLSConfig(
    certs_dir="/custom/certs",
    hostname="myagent.example.com",
    validity_days=365,
    renew_before_days=30,
)
app.run(port=8443, mtls=config)
Chainlit Chat UI with Streaming
import chainlit as cl
from kiboup import KiboAgentClient

SERVER_URL = "http://localhost:8080"
API_KEY = "sk-chat-abc"

@cl.on_chat_start
async def on_start():
    cl.user_session.set("history", [])

@cl.on_message
async def on_message(message: cl.Message):
    history = cl.user_session.get("history", [])
    history.append({"role": "user", "content": message.content})

    response = cl.Message(content="")
    await response.send()

    full_response = ""
    async with KiboAgentClient(SERVER_URL, api_key=API_KEY) as client:
        async for chunk in client.stream({
            "prompt": message.content,
            "messages": history,
        }):
            token = chunk.get("token")
            if token:
                full_response += token
                await response.stream_token(token)

    await response.update()
    history.append({"role": "assistant", "content": full_response})
Run It
# 1. Start streaming server
OPENAI_API_KEY=sk-... uv run python examples/stream_server_example.py

# 2. Start Chainlit
uv run chainlit run examples/chainlit_example.py

Get Started

Install KiboUP and start deploying agents in minutes.

1. Install

# Core (HTTP only)
uv add kiboup

# With all protocols + Studio
uv add "kiboup[all]"

2. Create an Agent

from kiboup import KiboAgentApp

app = KiboAgentApp()

@app.entrypoint
async def invoke(payload, context):
    return {"response": "Hello!"}

app.run(port=8080)