Skip to content

MrPrinceRawat/kanly

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

1 Commit
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Kanly

Deploy autonomous agents that use your tools, with guardrails you control.

Kanly is an open-source platform for running headless AI agents — triggered by APIs and webhooks, executing tools on your infrastructure, pausing for human approval when it matters. Every step is traced.

Not a chatbot framework. Not a personal assistant. A deployment target for agents that do real work.

Why Kanly

The problem: You want agents that can touch your real systems — run CLI commands, hit internal APIs, read your codebase. But you don't want to give an LLM full YOLO access to your infrastructure, and you don't want to babysit it in a terminal.

The solution: Kanly splits the brain from the hands. The agentic loop (LLM reasoning) runs on the server. Tool execution happens on your machine, behind your firewall, with your approval rules. Secrets never leave your infra.

  ┌─ Kanly Server ────────────────┐       ┌─ Your Machine ──────────────┐
  │                                │       │                             │
  │  Agent definitions             │  WS   │  Your tool handlers         │
  │  LLM orchestration loop       │◄─────►│  MCP servers                │
  │  Webhook tools (HTTP)          │       │  CLI commands               │
  │  Full trace capture            │       │  Approval gates             │
  │                                │       │                             │
  └────────────────────────────────┘       └─────────────────────────────┘

How it's different

Claude Code / Cursor OpenClaw LangGraph Cloud Kanly
Mode Interactive — you're in the terminal Chat — you message it Headless Headless
Trigger You type You message API API / webhook / cron
Tools Built-in Community skills (26% had vulnerabilities) Cloud-side Your code, your machine
Approval Popup (you're already there) Binary: full access or sandbox None Per-tool, async (Slack, terminal, webhook)
Agents One session One do-everything bot Many Many purpose-built agents
Secrets Local Local Cloud Never leave your machine

Example: Agent that fixes GitHub issues

An agent that reads a GitHub issue, explores the codebase, writes a fix, runs tests, and opens a PR — pausing for your approval before pushing.

1. Write your tool handlers:

# handlers.py
import subprocess, json

async def read_issue(arguments: dict) -> str:
    url = arguments["issue_url"]
    result = subprocess.run(["gh", "issue", "view", url, "--json", "title,body"], capture_output=True, text=True)
    return result.stdout

async def search_code(arguments: dict) -> str:
    result = subprocess.run(["rg", arguments["query"], "--type", "py", "-l"], capture_output=True, text=True)
    return result.stdout or "No matches found"

async def read_file(arguments: dict) -> str:
    with open(arguments["path"]) as f:
        return f.read()

async def edit_file(arguments: dict) -> str:
    with open(arguments["path"]) as f:
        content = f.read()
    content = content.replace(arguments["old"], arguments["new"])
    with open(arguments["path"], "w") as f:
        f.write(content)
    return "File updated"

async def run_tests(arguments: dict) -> str:
    result = subprocess.run(["pytest", "--tb=short"], capture_output=True, text=True)
    return result.stdout + result.stderr

async def create_pr(arguments: dict) -> str:
    subprocess.run(["git", "checkout", "-b", arguments["branch"]], check=True)
    subprocess.run(["git", "add", "-A"], check=True)
    subprocess.run(["git", "commit", "-m", arguments["title"]], check=True)
    return "Branch created and committed. Waiting for push approval."

2. Define the agent:

curl -X POST http://localhost:8000/agents \
  -H "Authorization: Bearer $KANLY_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "issue-fixer",
    "model": "anthropic/claude-sonnet-4",
    "system_prompt": "You fix GitHub issues. Read the issue, explore the code, write a fix, run tests. If tests pass, create a PR branch. Never push without approval.",
    "max_steps": 30,
    "tools": [
      {"type": "custom", "name": "read_issue", "description": "Read a GitHub issue", "parameters": {"type": "object", "properties": {"issue_url": {"type": "string"}}, "required": ["issue_url"]}},
      {"type": "custom", "name": "search_code", "description": "Search codebase with ripgrep", "parameters": {"type": "object", "properties": {"query": {"type": "string"}}, "required": ["query"]}},
      {"type": "custom", "name": "read_file", "description": "Read a file", "parameters": {"type": "object", "properties": {"path": {"type": "string"}}, "required": ["path"]}},
      {"type": "custom", "name": "edit_file", "description": "Edit a file by replacing text", "parameters": {"type": "object", "properties": {"path": {"type": "string"}, "old": {"type": "string"}, "new": {"type": "string"}}, "required": ["path", "old", "new"]}},
      {"type": "custom", "name": "run_tests", "description": "Run the test suite", "parameters": {"type": "object", "properties": {}}},
      {"type": "custom", "name": "create_pr", "description": "Create a branch and commit changes", "parameters": {"type": "object", "properties": {"branch": {"type": "string"}, "title": {"type": "string"}}, "required": ["branch", "title"]}},
      {"type": "cli", "name": "git_push", "command_pattern": "git push origin {args}", "auto_approve": false}
    ]
  }'

3. Start the runtime:

kanly-runtime connect --url http://localhost:8000 --api-key $KANLY_API_KEY --handlers handlers.py

4. Trigger it (from a GitHub webhook, cron, or manually):

curl -X POST http://localhost:8000/agents/issue-fixer/runs \
  -H "Authorization: Bearer $KANLY_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"message": "Fix this issue: https://github.com/yourorg/yourrepo/issues/42"}'

The agent works autonomously — reads the issue, explores code, writes a fix, runs tests. When it tries to git push, the runtime pauses and asks for your approval. You approve, the PR gets opened. Full trace of every step is captured.

Quickstart

1. Start the server

cd server && pip install -e .

export KANLY_API_KEY="your-secret-key"
export KANLY_OPENAI_API_KEY="sk-..."
export KANLY_OPENAI_BASE_URL="https://openrouter.ai/api/v1"

uvicorn kanly.app:app --host 0.0.0.0 --port 8000

2. Start the runtime

cd runtime && pip install -e .

kanly-runtime connect \
  --url http://localhost:8000 \
  --api-key your-secret-key \
  --handlers handlers.py

3. Create an agent and run it

# Create
curl -X POST http://localhost:8000/agents \
  -H "Authorization: Bearer your-secret-key" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "my-agent",
    "model": "anthropic/claude-sonnet-4",
    "system_prompt": "You are a helpful assistant.",
    "tools": [...]
  }'

# Run
curl -X POST http://localhost:8000/agents/my-agent/runs \
  -H "Authorization: Bearer your-secret-key" \
  -H "Content-Type: application/json" \
  -d '{"message": "Do the thing"}'

Configuration

Variable Description Required
KANLY_API_KEY API key for authenticating requests Yes
KANLY_OPENAI_API_KEY API key for the LLM provider Yes
KANLY_OPENAI_BASE_URL Base URL for OpenAI-compatible API Yes
KANLY_HANDLERS Path to custom tool handlers file (runtime) No

Works with any OpenAI-compatible API — OpenRouter, OpenAI, Anthropic (via proxy), local vLLM, Ollama, etc.

Tool Types

Custom

You define the schema, you write the handler. When the LLM calls the tool, Kanly dispatches it to your runtime.

{
  "type": "custom",
  "name": "query_db",
  "description": "Query the user database",
  "parameters": {
    "type": "object",
    "properties": {
      "sql": { "type": "string", "description": "SQL query to execute" }
    },
    "required": ["sql"]
  }
}
# handlers.py
async def query_db(arguments: dict) -> str:
    # your code, your database, your machine
    result = await db.execute(arguments["sql"])
    return json.dumps(result)

Webhook

Server-side HTTP call. No runtime needed. Good for external APIs.

{
  "type": "webhook",
  "name": "notify_slack",
  "url": "https://hooks.slack.com/services/...",
  "method": "POST"
}

CLI

Shell commands on your machine. auto_approve: false means the runtime pauses and asks before executing.

{
  "type": "cli",
  "name": "deploy",
  "command_pattern": "kubectl apply -f {args}",
  "auto_approve": false
}

MCP

Connect to any MCP server running on your machine.

{
  "type": "mcp",
  "server": "filesystem",
  "uri": "npx @modelcontextprotocol/server-filesystem /home/user"
}

Security

  • Tool allowlists — agents can only call tools in their tools array
  • Approval gates — CLI tools default to auto_approve: false, runtime pauses for human confirmation
  • No secret leakage — tools execute on your machine, credentials never touch the server
  • Shell injection prevention — CLI arguments are validated against injection patterns
  • Full audit trail — every LLM call, tool dispatch, and result is traced with timestamps

API Reference

All endpoints except /health require Authorization: Bearer <KANLY_API_KEY>.

Method Endpoint Description
POST /agents Create an agent
GET /agents List agents
GET /agents/{name} Get agent
PATCH /agents/{name} Update agent
DELETE /agents/{name} Delete agent
POST /agents/{name}/runs Trigger a run
GET /runs/{run_id} Get run status
GET /runs/{run_id}/trace Full execution trace
WS /runtime/ws Runtime WebSocket

The Name

Kanly is the formal war of assassins between Great Houses in Frank Herbert's Dune — regulated conflict with clear rules of engagement. Agents with rules.

License

MIT. See LICENSE.

About

Deploy autonomous agents that use your tools, with guardrails you control.

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Languages