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]"
Choose the right protocol for your use case. All share authentication, logging, and Studio integration.
Standard REST API with streaming (SSE), WebSocket, task tracking, and health checks. Works with any frontend.
POST /invocations
GET /ping
WS /ws
Expose agent capabilities as discoverable tools via the Model Context Protocol. Compatible with MCP Inspector and IDEs.
@app.tool()
@app.resource()
@app.prompt()
Google's Agent-to-Agent protocol with auto-generated Agent Cards, skill-based routing, and task lifecycle.
/.well-known/agent.json
@app.executor
Full observability, prompt management, evaluation, and agent discovery in a single web interface.
Service registry where agents register, send heartbeats, and discover each other at runtime. Monitor health, uptime, memory, and capabilities from the Dashboard.
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.
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.
Built-in chat to test any registered agent directly from the browser. Full markdown rendering, message history, and real-time responses.
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.
Version-controlled prompt templates with variable extraction, active version selection, and model configuration. Fetch prompts at runtime via the SDK.
Automated quality evaluation using GPT-4o-mini. Scores traces on answer relevancy, coherence, completeness, and harmfulness (0.0-1.0).
Full async Python SDK for agents. Auto-registration, heartbeats, flag/param caching (30s TTL), and inline integration with KiboAgentClient and KiboMcpClient.
Zero-config persistence with SQLite WAL mode. No external databases required. Just point to a file path and go.
from kiboup.studio import KiboStudio
studio = KiboStudio(db_path="kibostudio.db", debug=True)
studio.run(host="0.0.0.0", port=8000)
Ready-to-run examples covering every protocol and feature.
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)
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 -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?"}'
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)
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())
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")
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())
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()
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())
// GET /.well-known/agent.json
{
"name": "Chat Agent",
"description": "GPT-4o-mini via LangGraph",
"skills": [{
"id": "chat",
"name": "Chat",
"tags": ["chat", "qa"]
}]
}
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)
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)
# 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
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)
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())
# 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)
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})
# 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
Install KiboUP and start deploying agents in minutes.
# Core (HTTP only)
uv add kiboup
# With all protocols + Studio
uv add "kiboup[all]"
from kiboup import KiboAgentApp
app = KiboAgentApp()
@app.entrypoint
async def invoke(payload, context):
return {"response": "Hello!"}
app.run(port=8080)