Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 4 additions & 4 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -67,11 +67,11 @@ jobs:
- name: Set up environment
uses: ./.github/actions/setup-python-env

- name: Build kai
run: uv build --package kai --no-sources --out-dir dist/kai
- name: Build kcastle-ai
run: uv build --package kcastle-ai --no-sources --out-dir dist/kcastle-ai

- name: Build kagent
run: uv build --package kagent --no-sources --out-dir dist/kagent
- name: Build kcastle-agent
run: uv build --package kcastle-agent --no-sources --out-dir dist/kcastle-agent

- name: Build kcastle
run: uv build --package kcastle --no-sources --out-dir dist/kcastle
Expand Down
103 changes: 103 additions & 0 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
name: Release

on:
release:
types: [published]

concurrency:
group: release
cancel-in-progress: false

jobs:
build:
runs-on: ubuntu-latest
steps:
- name: Check out
uses: actions/checkout@v6

- name: Set up environment
uses: ./.github/actions/setup-python-env

- name: Build kcastle-ai
run: uv build --package kcastle-ai --no-sources --out-dir dist/kcastle-ai

- name: Build kcastle-agent
run: uv build --package kcastle-agent --no-sources --out-dir dist/kcastle-agent

- name: Build kcastle
run: uv build --package kcastle --no-sources --out-dir dist/kcastle

- name: Upload kcastle-ai artifacts
uses: actions/upload-artifact@v4
with:
name: dist-kcastle-ai
path: dist/kcastle-ai/

- name: Upload kcastle-agent artifacts
uses: actions/upload-artifact@v4
with:
name: dist-kcastle-agent
path: dist/kcastle-agent/

- name: Upload kcastle artifacts
uses: actions/upload-artifact@v4
with:
name: dist-kcastle
path: dist/kcastle/

publish-kcastle-ai:
runs-on: ubuntu-latest
needs: build
permissions:
id-token: write
environment: pypi
steps:
- name: Download artifacts
uses: actions/download-artifact@v4
with:
name: dist-kcastle-ai
path: dist/

- name: Publish to PyPI
uses: pypa/gh-action-pypi-publish@release/v1
with:
packages-dir: dist/
skip-existing: true

publish-kcastle-agent:
runs-on: ubuntu-latest
needs: publish-kcastle-ai
permissions:
id-token: write
environment: pypi
steps:
- name: Download artifacts
uses: actions/download-artifact@v4
with:
name: dist-kcastle-agent
path: dist/

- name: Publish to PyPI
uses: pypa/gh-action-pypi-publish@release/v1
with:
packages-dir: dist/
skip-existing: true

publish-kcastle:
runs-on: ubuntu-latest
needs: publish-kcastle-agent
permissions:
id-token: write
environment: pypi
steps:
- name: Download artifacts
uses: actions/download-artifact@v4
with:
name: dist-kcastle
path: dist/

- name: Publish to PyPI
uses: pypa/gh-action-pypi-publish@release/v1
with:
packages-dir: dist/
skip-existing: true
36 changes: 21 additions & 15 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,24 +1,30 @@
# K
# K in Castle

Tools for building AI agents in Python.
K is an agent living in a castle.

## Packages
> [!WARNING]
> K has not formally entered Castle yet. The project is still in a rapid
> development stage, and breaking changes may be introduced at any time.

| Package | Description |
|---------|-------------|
| **[kai](packages/kai)** | Unified multi-provider LLM API |
| **[kagent](packages/kagent)** | Agent runtime with tool calling and state management |
| **[kcastle](packages/kcastle)** | Agent application with multi-endpoint support |
## Quick Start

## Development
Install as a uv tool:

```bash
uv sync # Install all dependencies
uv run pytest # Run tests
uv run ruff check # Lint
uv run pyright # Type check
uv tool install kcastle
```

## License
Then run:

```bash
kcastle
```


## Packages

MIT
| Package | PyPI | Description |
|---------|------|-------------|
| **[kai](packages/kai)** | [![PyPI](https://img.shields.io/pypi/v/kcastle-ai?color=%2334D058&label=pypi%20package)](https://pypi.org/project/kcastle-ai/) | Unified multi-provider LLM API |
| **[kagent](packages/kagent)** | [![PyPI](https://img.shields.io/pypi/v/kcastle-agent?color=%2334D058&label=pypi%20package)](https://pypi.org/project/kcastle-agent/) | Agent runtime with tool calling and state management |
| **[kcastle](packages/kcastle)** | [![PyPI](https://img.shields.io/pypi/v/kcastle?color=%2334D058&label=pypi%20package)](https://pypi.org/project/kcastle/) | Agent application with multi-endpoint support |
2 changes: 2 additions & 0 deletions packages/kagent/README.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
# kagent

[![PyPI](https://img.shields.io/pypi/v/kcastle-agent?color=%2334D058&label=pypi%20package)](https://pypi.org/project/kcastle-agent/)

Core agent runtime for the K agent framework — a context-first, three-level agent SDK built on `kai`.

## Key Principles
Expand Down
8 changes: 4 additions & 4 deletions packages/kagent/pyproject.toml
Original file line number Diff line number Diff line change
@@ -1,14 +1,14 @@
[project]
name = "kagent"
version = "0.1.0"
name = "kcastle-agent"
version = "0.0.1a0"
description = "Core agent framework for the k agent framework"
readme = "README.md"
authors = [
{ name = "Xiangzhuang Shen", email = "datahonor@gmail.com" }
]
requires-python = ">=3.12"
dependencies = [
"kai",
"kcastle-ai",
]

[project.optional-dependencies]
Expand All @@ -35,7 +35,7 @@ module-name = ["kagent"]
source-exclude = ["tests/**/*"]

[tool.uv.sources]
kai = { workspace = true }
"kcastle-ai" = { workspace = true }

[tool.ruff]
line-length = 100
Expand Down
4 changes: 3 additions & 1 deletion packages/kai/README.md
Original file line number Diff line number Diff line change
@@ -1,13 +1,15 @@
# kai

[![PyPI](https://img.shields.io/pypi/v/kcastle-ai?color=%2334D058&label=pypi%20package)](https://pypi.org/project/kcastle-ai/)

Unified multi-provider LLM API for the k agent framework.

kai provides a simple, provider-agnostic interface for streaming LLM completions with tool calling support. It has two entry points — `stream()` for real-time events and `complete()` for one-shot responses.

## Installation

```bash
uv add kai
uv add kcastle-ai
```

## Quick Start
Expand Down
4 changes: 2 additions & 2 deletions packages/kai/pyproject.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[project]
name = "kai"
version = "0.1.0"
name = "kcastle-ai"
version = "0.0.1a0"
description = "AI provider abstractions for the k agent framework"
readme = "README.md"
authors = [
Expand Down
2 changes: 2 additions & 0 deletions packages/kcastle/README.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
# kcastle

[![PyPI](https://img.shields.io/pypi/v/kcastle?color=%2334D058&label=pypi%20package)](https://pypi.org/project/kcastle/)

Agent application with multi-endpoint support for the K agent framework.

kcastle is a general-purpose agent application built on `kagent`. It provides a unified
Expand Down
28 changes: 14 additions & 14 deletions packages/kcastle/docs/arch.md
Original file line number Diff line number Diff line change
Expand Up @@ -121,7 +121,7 @@ The ID format depends on who creates the session:
| Creator | Session ID format | Example |
|---------|------------------|---------|
| CLI (default) | UUID hex (short) | `a1b2c3d4` |
| CLI (`k -S <id>`) | user-specified | `my-project` |
| CLI (`kcastle -S <id>`) | user-specified | `my-project` |
| Telegram private | `tg-u{user_id}` | `tg-u123456` |
| Telegram group | `tg-g{chat_id}` | `tg-g-987654` |

Expand All @@ -130,13 +130,13 @@ directly — no lookup table needed. Telegram private chats and groups each get
their own session automatically.

**Cross-channel resume**: any channel can resume any session by its ID.
`k -S tg-u123456` opens a Telegram user's session from the CLI. This works
`kcastle -S tg-u123456` opens a Telegram user's session from the CLI. This works
naturally because the session directory is the same regardless of which
channel accesses it.

**Session resolution heuristics** (no persisted mapping):
- **CLI** `k -C`: resume the most recently active session (by `last_active_at`).
- **CLI** `k -S <id>`: explicit session selection.
- **CLI** `kcastle -C`: resume the most recently active session (by `last_active_at`).
- **CLI** `kcastle -S <id>`: explicit session selection.
- **Telegram**: deterministic `tg-u{user_id}` / `tg-g{chat_id}` — auto-created
on first message, resumed on subsequent messages.

Expand All @@ -146,7 +146,7 @@ channel accesses it.
kcastle/
├── __init__.py # Public API re-exports
├── castle.py # Castle — top-level lifecycle orchestrator
├── cli.py # `k` command entry point
├── cli.py # `kcastle` command entry point
├── config.py # CastleConfig, YAML + env var loading, built-in registry
├── daemon.py # Daemon process management (start/stop/status/restart)
├── setup.py # First-run env var detection + config generation
Expand Down Expand Up @@ -238,7 +238,7 @@ At load time, `load_config` merges built-in providers with user config:

### First-run setup

On first launch, `k` detects that `config.yaml` is missing and runs a
On first launch, `kcastle` detects that `config.yaml` is missing and runs a
non-interactive setup:

1. Scan for known API key env vars (`DEEPSEEK_API_KEY`, `MINIMAX_API_KEY`).
Expand Down Expand Up @@ -498,15 +498,15 @@ Castle
Interactive terminal using `prompt_toolkit`.

```
$ k # New session (auto-generated ID)
$ k -C # Continue most recently active session
$ k -S <id> # Resume specific session by ID
$ k -d # Daemon mode (foreground, no interactive CLI)
$ kcastle # New session (auto-generated ID)
$ kcastle -C # Continue most recently active session
$ kcastle -S <id> # Resume specific session by ID
$ kcastle -d # Daemon mode (foreground, no interactive CLI)

$ k start # Start daemon in background
$ k stop # Stop the background daemon
$ k status # Show daemon status
$ k restart # Restart the daemon
$ kcastle start # Start daemon in background
$ kcastle stop # Stop the background daemon
$ kcastle status # Show daemon status
$ kcastle restart # Restart the daemon

k> hello # User input → session.run("hello")
# Stream AgentEvent → render to terminal
Expand Down
8 changes: 4 additions & 4 deletions packages/kcastle/pyproject.toml
Original file line number Diff line number Diff line change
@@ -1,21 +1,21 @@
[project]
name = "kcastle"
version = "0.1.0"
version = "0.0.1a0"
description = "Agent application with multi-endpoint support for the k agent framework"
readme = "README.md"
authors = [
{ name = "Xiangzhuang Shen", email = "datahonor@gmail.com" }
]
requires-python = ">=3.12"
dependencies = [
"kagent",
"kcastle-agent",
"pyyaml>=6.0",
"python-telegram-bot>=21.0",
"telegramify-markdown>=0.5.4",
]

[project.scripts]
k = "kcastle.cli:main"
kcastle = "kcastle.cli:main"

[dependency-groups]
dev = [
Expand All @@ -30,7 +30,7 @@ requires = ["uv_build>=0.9.18,<0.10.0"]
build-backend = "uv_build"

[tool.uv.sources]
kagent = { workspace = true }
"kcastle-agent" = { workspace = true }

[tool.ruff]
line-length = 100
Expand Down
6 changes: 3 additions & 3 deletions packages/kcastle/src/kcastle/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,9 @@

Or from the command line::

$ k # New session
$ k -C # Continue latest session
$ k -S <id> # Resume specific session
$ kcastle # New session
$ kcastle -C # Continue latest session
$ kcastle -S <id> # Resume specific session
"""

from kcastle.castle import Castle
Expand Down
6 changes: 3 additions & 3 deletions packages/kcastle/src/kcastle/channels/cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,9 @@

Usage::

$ k # New session (auto-generated ID)
$ k -C # Continue most recently active session
$ k -S <id> # Resume specific session by ID
$ kcastle # New session (auto-generated ID)
$ kcastle -C # Continue most recently active session
$ kcastle -S <id> # Resume specific session by ID
"""

from __future__ import annotations
Expand Down
20 changes: 10 additions & 10 deletions packages/kcastle/src/kcastle/cli/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,14 @@

Usage::

$ k # New session (auto-generated ID)
$ k -C # Continue most recently active session
$ k -S <id> # Resume specific session by ID
$ k -d # Daemon mode (no interactive CLI, foreground)
$ k start # Start daemon in background
$ k stop # Stop the background daemon
$ k status # Show daemon status
$ k restart # Restart the daemon
$ kcastle # New session (auto-generated ID)
$ kcastle -C # Continue most recently active session
$ kcastle -S <id> # Resume specific session by ID
$ kcastle -d # Daemon mode (no interactive CLI, foreground)
$ kcastle start # Start daemon in background
$ kcastle stop # Stop the background daemon
$ kcastle status # Show daemon status
$ kcastle restart # Restart the daemon
"""

from __future__ import annotations
Expand All @@ -21,9 +21,9 @@


def main() -> None:
"""Main entry point for the ``k`` command."""
"""Main entry point for the ``kcastle`` command."""
parser = argparse.ArgumentParser(
prog="k",
prog="kcastle",
description="kcastle — AI agent with session management",
)
parser.add_argument(
Expand Down
Loading