Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
31 commits
Select commit Hold shift + click to select a range
6259880
chore: enable ruff PERF rules and fix violations
bluetoothbot May 16, 2026
2495dff
fix(ci): ignore skipped Dependabot auto-merge runs in CI status
aiolibsbot May 16, 2026
911ac87
feat: route security audit findings to PVRS when available
Koan-Bot May 16, 2026
04476fc
fix: redact PVRS fallback issues to prevent leaking vulnerability det…
Koan-Bot May 16, 2026
7aaec0d
feat(git): sync all remotes before branch work
toddr-bot May 15, 2026
3b39ba7
fix: address review — remove double-fetch, fix fragile rebase tests
toddr-bot May 16, 2026
97fcac3
fix: resolve CI failures on #1334 (attempt 1)
toddr-bot May 16, 2026
5d1afc6
docs: enforce ruff linting in CLAUDE.md and add make lint target
Koan-Bot May 16, 2026
0d2925f
feat(messaging): add Matrix provider alongside Telegram and Slack
bdraco May 16, 2026
2e3fac7
docs(matrix): advertise instance/config.yaml as primary, env vars as …
aiolibsbot May 16, 2026
f28d0c4
lint: enable ruff SIM105 and convert try/except/pass to contextlib.su…
bluetoothbot May 16, 2026
767c25d
feat(ci): detect workflows blocked on maintainer approval and stop re…
bluetoothbot May 16, 2026
be2aea3
fix(rebase): stream run_claude stdout so liveness watchdog sees output
bluetoothbot May 16, 2026
d5ff6d8
refactor(cli_exec): extract stream_with_timeout helper and fix watchd…
bluetoothbot May 16, 2026
b21a5ab
refactor(cli_exec): use contextlib.suppress in stream_with_timeout cl…
bluetoothbot May 16, 2026
3a4cbff
fix: use atomic writes for crash-sensitive file operations
Koan-Bot May 15, 2026
1ca92b4
refactor(ci): introduce CI_STATUS_BLOCKED_APPROVAL constant
Koan-Bot May 17, 2026
ec2ab67
test: isolate tests so they can run in parallel under pytest-xdist
bluetoothbot May 16, 2026
4d29b39
test(perf): parallelize the test suite with pytest-xdist
bluetoothbot May 16, 2026
a87abf7
test: address review feedback on pytest-xdist isolation fixtures
bluetoothbot May 17, 2026
dbdca5e
test: mock resolve_pr_location in conftest to fix xdist gh flakes
bluetoothbot May 17, 2026
77e6d14
test(memory_manager): verify _get_file_tree skipped below compact thr…
Koan-Bot May 17, 2026
270e114
feat(missions): add duplicate detection for GitHub-action missions
Koan-Bot May 16, 2026
6f6a4d4
fix(missions): handle duplicate return value in all skill handlers
Koan-Bot May 17, 2026
50e9a89
refactor(missions): extract queue_github_mission_once helper to reduc…
Koan-Bot May 17, 2026
305b045
fix(skills): reload modules updated since process boot
atoomic May 17, 2026
cb6e927
fix(restart): per-process restart markers
atoomic May 17, 2026
87a3aa6
fix(messaging): load every provider module on first call
atoomic May 17, 2026
7b05b09
fix(ci): use less alarming emoji for CI failure notifications
Koan-Bot May 17, 2026
8de4361
feat(review): flag merge conflicts in posted review comment
aiolibsbot May 17, 2026
3f5a2f5
chore(cli_exec): collapse nested try/except into contextlib.suppress
aiolibsbot May 17, 2026
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
4 changes: 3 additions & 1 deletion .github/workflows/tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ jobs:
- name: Install dependencies
run: |
pip install -r koan/requirements.txt
pip install pytest pytest-split pytest-cov
pip install pytest pytest-split pytest-cov pytest-xdist

- name: Run tests (${{ matrix.group.name }})
if: ${{ !inputs.group || matrix.group.name == inputs.group }}
Expand All @@ -66,9 +66,11 @@ jobs:
run: |
if [ -n "${{ matrix.group.split_group }}" ]; then
pytest tests/ -m "${{ matrix.group.marker }}" --splits 3 --group ${{ matrix.group.split_group }} -v \
-n auto --dist loadfile \
--cov=app --cov-report=term-missing
else
pytest tests/ -m "${{ matrix.group.marker }}" -v \
-n auto --dist loadfile \
--cov=app --cov-report=term-missing
fi

Expand Down
11 changes: 11 additions & 0 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ make run # Start main agent loop (foreground)
make awake # Start Telegram bridge (foreground)
make ollama # Start full Ollama stack (ollama serve + awake + run)
make dashboard # Start Flask web dashboard (port 5001)
make lint # Run ruff linter (must pass before committing)
make test # Run full test suite (pytest + coverage summary)
make coverage # Run tests with detailed coverage report (HTML in htmlcov/)
make say m="..." # Send test message as if from Telegram
Expand Down Expand Up @@ -142,6 +143,16 @@ Extensible command plugin system. Each skill lives in `skills/<scope>/<skill-nam

All code must support **Python 3.11+**. Do not use syntax or stdlib features introduced after Python 3.11 (e.g., `type` statements from 3.12, `TypeVar` defaults from 3.13). CI tests against multiple Python versions — if it doesn't run on 3.11, it doesn't ship.

## Linting

All Python code must pass **ruff** (`make lint`) before committing. The ruff configuration lives in `pyproject.toml` under `[tool.ruff]`.

- Run `make lint` to check for violations. Fix all errors before pushing.
- Currently enforced rule sets: **PERF** (performance anti-patterns). New rule sets will be added incrementally as existing violations are cleaned up.
- Test files (`koan/tests/*`) are exempt from PERF rules via `per-file-ignores`.
- When adding new code, avoid introducing violations from rule sets not yet enforced project-wide (E, F, W, I, B are good hygiene even though not yet gated in CI).
- Do not disable ruff rules with `# noqa` comments unless there is a clear, documented reason. Prefer fixing the violation.

## Conventions

- Claude always creates **`<prefix>/*` branches** (default `koan/`, configurable via `branch_prefix` in `config.yaml`), never commits to main
Expand Down
28 changes: 26 additions & 2 deletions INSTALL.md
Original file line number Diff line number Diff line change
Expand Up @@ -87,14 +87,15 @@ The `instance/` directory is your private data — it's gitignored and never pus

### 2. Set up a messaging platform

Kōan supports **Telegram** (default) and **Slack** for communication. Follow the setup guide for your preferred platform:
Kōan supports **Telegram** (default), **Slack**, and **Matrix** for communication. Follow the setup guide for your preferred platform:

| Platform | Setup Guide | Best For |
|----------|-------------|----------|
| **Telegram** (default) | [docs/messaging-telegram.md](docs/messaging-telegram.md) | Quick setup, works from any network |
| **Slack** | [docs/messaging-slack.md](docs/messaging-slack.md) | Team collaboration, workspace integration |
| **Matrix** | [docs/messaging-matrix.md](docs/messaging-matrix.md) | Self-hosted / federated, open protocol |

Both platforms are fully supported with the same feature set. Telegram is recommended for personal use (simpler setup), while Slack is ideal for team environments.
All three platforms expose the same feature set. Telegram is the simplest for personal use, Slack is best for team environments, and Matrix is ideal if you want a self-hosted or federated option.

### 3. Set environment variables

Expand All @@ -118,6 +119,29 @@ KOAN_SLACK_APP_TOKEN=xapp-your-app-token
KOAN_SLACK_CHANNEL_ID=C01234ABCD
```

**For Matrix:** Matrix can be configured via `.env` *or* via `instance/config.yaml` (recommended — see [docs/messaging-matrix.md](docs/messaging-matrix.md) for the full guide):

```yaml
# instance/config.yaml (recommended)
messaging:
provider: "matrix"
matrix:
homeserver: "https://matrix.org"
user_id: "@koan:matrix.org"
room_id: "!abcdefghijk:matrix.org"
access_token: "syt_your_token_here"
```

Or the legacy `.env` form (env vars override `config.yaml` when set):

```bash
KOAN_MESSAGING_PROVIDER=matrix
KOAN_MATRIX_HOMESERVER=https://matrix.org
KOAN_MATRIX_ACCESS_TOKEN=syt_your_token_here
KOAN_MATRIX_USER_ID=@koan:matrix.org
KOAN_MATRIX_ROOM_ID=!abcdefghijk:matrix.org
```

The `.env` file is gitignored — your secrets stay local. See the provider-specific setup guides above for detailed instructions on obtaining these credentials.

### 4. Configure projects
Expand Down
45 changes: 36 additions & 9 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
export

.PHONY: install onboard setup start stop status restart
.PHONY: clean say migrate test test-skills test-strict coverage sync-instance rename-project release
.PHONY: clean say migrate test test-skills test-strict coverage lint sync-instance rename-project release
.PHONY: awake run errand-run errand-awake dashboard
.PHONY: ollama logs ssh-forward
.PHONY: install-systemctl-service uninstall-systemctl-service
Expand All @@ -14,6 +14,28 @@ PYTHON_BIN ?= python3
VENV ?= .venv
PYTHON ?= $(VENV)/bin/$(PYTHON_BIN)

# --- pytest-xdist worker count ---
# Auto-pick the worker count for `make test` based on the environment:
# * CI / GitHub Actions → all available cores (`-n auto`)
# * Remote SSH session → 2 workers (be polite on shared hosts)
# * Local terminal → all available cores (`-n auto`)
# Override anytime with `make test PYTEST_WORKERS=N` (use 0 to disable xdist).
ifneq ($(CI),)
PYTEST_WORKERS ?= auto
else ifneq ($(GITHUB_ACTIONS),)
PYTEST_WORKERS ?= auto
else ifneq ($(SSH_CONNECTION)$(SSH_CLIENT)$(SSH_TTY),)
PYTEST_WORKERS ?= 2
else
PYTEST_WORKERS ?= auto
endif

ifeq ($(PYTEST_WORKERS),0)
PYTEST_XDIST_ARGS :=
else
PYTEST_XDIST_ARGS := -n $(PYTEST_WORKERS) --dist loadfile
endif

# --- service manager detection ---
# Default: foreground processes via pid_manager (no service manager)
# Set KOAN_SERVICE_MANAGER=systemd or KOAN_SERVICE_MANAGER=launchd in .env to opt in
Expand Down Expand Up @@ -49,27 +71,32 @@ say: setup
@test -n "$(m)" || (echo "Usage: make say m=\"your message\"" && exit 1)
@cd koan && KOAN_ROOT=$(PWD) PYTHONPATH=. ../$(PYTHON) -c "from app.awake import handle_message; handle_message('$(m)')"

lint: setup
$(VENV)/bin/pip install -q ruff 2>/dev/null
$(VENV)/bin/ruff check koan/

test: setup
$(VENV)/bin/pip install -q pytest pytest-cov 2>/dev/null
cd koan && KOAN_ROOT=/tmp/test-koan PYTHONPATH=. ../$(PYTHON) -m pytest tests/ -v --cov=app --cov-report=term-missing --cov-report=html:htmlcov
@echo "→ pytest workers: $(PYTEST_WORKERS)"
$(VENV)/bin/pip install -q pytest pytest-cov pytest-xdist 2>/dev/null
cd koan && KOAN_ROOT=/tmp/test-koan PYTHONPATH=. ../$(PYTHON) -m pytest tests/ -v $(PYTEST_XDIST_ARGS) --cov=app --cov-report=term-missing --cov-report=html:htmlcov
@$(MAKE) --no-print-directory test-skills

test-skills: setup
@if [ -d instance/skills ] && find -L instance/skills -path '*/tests/test_*.py' -print -quit 2>/dev/null | grep -q .; then \
$(VENV)/bin/pip install -q pytest pytest-cov 2>/dev/null; \
$(VENV)/bin/pip install -q pytest pytest-cov pytest-xdist 2>/dev/null; \
echo "→ running skill-local tests (instance/skills/**/tests)"; \
KOAN_REPO=$(PWD) KOAN_ROOT=/tmp/test-koan PYTHONPATH=koan $(PYTHON) -m pytest instance/skills/ -v; \
KOAN_REPO=$(PWD) KOAN_ROOT=/tmp/test-koan PYTHONPATH=koan $(PYTHON) -m pytest instance/skills/ -v $(PYTEST_XDIST_ARGS); \
else \
echo "→ no skill-local tests found under instance/skills/**/tests/ — skipping"; \
fi

test-strict: setup
@echo "→ running full test suite in strict mode (0 failures required)"
$(VENV)/bin/pip install -q pytest pytest-cov 2>/dev/null
@cd koan && KOAN_ROOT=/tmp/test-koan PYTHONPATH=. ../$(PYTHON) -m pytest tests/ -q --tb=short \
@echo "→ running full test suite in strict mode (0 failures required, workers: $(PYTEST_WORKERS))"
$(VENV)/bin/pip install -q pytest pytest-cov pytest-xdist 2>/dev/null
@cd koan && KOAN_ROOT=/tmp/test-koan PYTHONPATH=. ../$(PYTHON) -m pytest tests/ -q --tb=short $(PYTEST_XDIST_ARGS) \
|| (echo "✗ tests failed — aborting" && exit 1)
@if [ -d instance/skills ] && find -L instance/skills -path '*/tests/test_*.py' -print -quit 2>/dev/null | grep -q .; then \
KOAN_REPO=$(PWD) KOAN_ROOT=/tmp/test-koan PYTHONPATH=koan $(PYTHON) -m pytest instance/skills/ -q --tb=short \
KOAN_REPO=$(PWD) KOAN_ROOT=/tmp/test-koan PYTHONPATH=koan $(PYTHON) -m pytest instance/skills/ -q --tb=short $(PYTEST_XDIST_ARGS) \
|| (echo "✗ skill-local tests failed — aborting" && exit 1); \
fi
@echo "✓ all tests passed"
Expand Down
8 changes: 4 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@

You pay for AI coding quota. You use it 8 hours a day. The other 16? Wasted quota.

Koan fixes that. It's a background agent that runs on your machine, pulls tasks from a shared mission queue, executes them via your configured CLI provider (Claude Code, Codex, Copilot, or local), and reports back through Telegram or Slack. It writes code in isolated branches, never touches `main`, and waits for your review before anything ships.
Koan fixes that. It's a background agent that runs on your machine, pulls tasks from a shared mission queue, executes them via your configured CLI provider (Claude Code, Codex, Copilot, or local), and reports back through Telegram, Slack, or Matrix. It writes code in isolated branches, never touches `main`, and waits for your review before anything ships.

**The agent proposes. The human decides.**

Expand Down Expand Up @@ -98,7 +98,7 @@ But Koan takes a different path entirely.
| **Getting started** | `npm install -g openclaw` + onboarding wizard | TOML config, pairing codes, allowlists | `make install` — interactive web wizard, ready in minutes |
| **Safety model** | Pairing codes, sandbox optional — but has shell access, browser control, and can send emails autonomously | Mandatory sandboxing, command allowlists, encrypted keys | Branch isolation, draft PRs only, never touches `main`, human review required |
| **Memory** | Local Markdown files, session persistence | Hybrid BM25/vector search, multiple backends | Markdown-based — per-project learnings, session journals, personality evolution. No database needed |
| **Communication** | 21+ channels (WhatsApp, Telegram, Slack, Discord, iMessage, Signal…) | 15+ channels (Telegram, Discord, Slack, iMessage…) | Telegram/Slack with personality-aware formatting, spontaneous messages, and verbose mode |
| **Communication** | 21+ channels (WhatsApp, Telegram, Slack, Discord, iMessage, Signal…) | 15+ channels (Telegram, Discord, Slack, iMessage…) | Telegram, Slack, or Matrix with personality-aware formatting, spontaneous messages, and verbose mode |
| **Quota awareness** | No | No | Adapts work depth to remaining API quota (DEEP → IMPLEMENT → REVIEW → WAIT) |
| **Extensibility** | 100+ AgentSkills, skill marketplace, 50+ integrations | Trait-based plugin system | 44 built-in skills + pluggable skill system (install from Git repos) |
| **Scope** | Everything — emails, web browsing, car negotiations, legal filings | Everything — any LLM task in any context | One thing, done right — autonomous GitHub collaboration |
Expand All @@ -108,7 +108,7 @@ OpenClaw and ZeroClaw are general-purpose autonomous agents that can do *anythin
## How It Works

```
You (Telegram/Slack)
You (Telegram/Slack/Matrix)
┌─────────────────┐ ┌──────────────────┐
Expand Down Expand Up @@ -168,7 +168,7 @@ Communication happens through shared markdown files in `instance/` — atomic wr

### Communication

- **Telegram & Slack** — Pluggable messaging with flood protection
- **Telegram, Slack & Matrix** — Pluggable messaging with flood protection
- **Email digests** — Optional SMTP email notifications for session summaries (rate-limited, deduplicated)
- **Personality-aware formatting** — Every outbox message passes through Claude with soul + memory context
- **Verbose mode** — Real-time progress updates streamed to your phone
Expand Down
1 change: 1 addition & 0 deletions docs/github-commands.md
Original file line number Diff line number Diff line change
Expand Up @@ -325,5 +325,6 @@ See [Jira Integration](jira-integration.md) for full setup instructions and the
- [Skills README](../koan/skills/README.md) — Skill authoring guide with `github_enabled` flag documentation
- [Messaging: Telegram](messaging-telegram.md) — Alternative command interface via Telegram
- [Messaging: Slack](messaging-slack.md) — Alternative command interface via Slack
- [Messaging: Matrix](messaging-matrix.md) — Alternative command interface via Matrix
- [PR #251](https://github.com/sukria/koan/pull/251) — Original implementation
- [Issue #243](https://github.com/sukria/koan/issues/243) — Feature request and design plan
1 change: 1 addition & 0 deletions docs/jira-integration.md
Original file line number Diff line number Diff line change
Expand Up @@ -360,5 +360,6 @@ Expected behavior. The in-memory processed set is lost on restart, but the persi
- [GitHub Notification Commands](github-commands.md) — GitHub @mention integration (complementary)
- [Messaging: Telegram](messaging-telegram.md) — Primary command interface
- [Messaging: Slack](messaging-slack.md) — Alternative messaging provider
- [Messaging: Matrix](messaging-matrix.md) — Alternative messaging provider
- [Skills Reference](skills.md) — Full skill documentation
- [User Manual](user-manual.md) — Complete usage guide
123 changes: 123 additions & 0 deletions docs/messaging-matrix.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,123 @@
# Matrix Setup Guide

This guide covers setting up Kōan with [Matrix](https://matrix.org) as the messaging provider. Kōan talks to a Matrix homeserver via the Client-Server HTTP API — no extra Python packages are required beyond `requests`.

## Prerequisites

- Access to a Matrix homeserver. You can use [matrix.org](https://matrix.org), a self-hosted Synapse/Dendrite/Conduit, or any compliant server.
- A dedicated Matrix account for the bot (recommended — don't reuse your personal account).
- An Element (or other Matrix client) login for the bot account, to invite it into the operating room.

## Step 1: Create a Bot Account

Either register a new account directly on the homeserver or use an existing dedicated account. The user ID will look like `@koan:matrix.org`.

## Step 2: Obtain an Access Token

The easiest way is to log in via Element with the bot account, then:

1. Open Element → **Settings → Help & About**
2. Scroll to the bottom and expand **Access Token**
3. Copy the token (long string starting with `syt_`, `mat_`, or similar)

Alternatively, use the `/login` API endpoint:

```bash
curl -XPOST -d '{
"type": "m.login.password",
"user": "koan",
"password": "YOUR_BOT_PASSWORD"
}' "https://matrix.org/_matrix/client/v3/login"
```

The response contains an `access_token` field.

> **Security note:** The access token grants full account access. Treat it like a password — never commit it. If leaked, log out the session via Element (**Settings → Sessions**) to invalidate it.

## Step 3: Create or Choose a Room

Pick the room Kōan will operate in. Either:

- Create a new private room in Element and invite the bot.
- Use an existing room and invite the bot.

Get the room ID:

1. In Element, open the room
2. Click the room name → **Settings → Advanced**
3. Copy the **Internal room ID** (e.g., `!abcdefghijk:matrix.org`)

Make sure the bot account has joined the room (accept the invite from the bot's session, or call `/_matrix/client/v3/join/{roomId}`).

## Step 4: Configure Kōan

The recommended approach is to put Matrix settings in `instance/config.yaml`:

```yaml
messaging:
provider: "matrix"
matrix:
homeserver: "https://matrix.org"
user_id: "@koan:matrix.org"
room_id: "!abcdefghijk:matrix.org"
access_token: "syt_your_token_here"
```

> Treat `instance/config.yaml` like a secret file — it's gitignored by default. If you commit your `instance/` directory to a separate private repo, that's fine; never commit the access token to a public repo.

### Legacy: environment variables

The four `KOAN_MATRIX_*` env vars are still supported and override `config.yaml` when set. Use them only if you have a workflow built around `.env`:

```bash
# .env (legacy alternative)
KOAN_MESSAGING_PROVIDER=matrix
KOAN_MATRIX_HOMESERVER=https://matrix.org
KOAN_MATRIX_ACCESS_TOKEN=syt_your_token_here
KOAN_MATRIX_USER_ID=@koan:matrix.org
KOAN_MATRIX_ROOM_ID=!abcdefghijk:matrix.org
```

Precedence: env var > `config.yaml` value > error.

## Step 5: Start Kōan

```bash
make start
```

You should see in the logs:

```
[init] Messaging provider: MATRIX, Channel: !abcdefghijk:matrix.org
```

## How it works

- **Sending**: `PUT /_matrix/client/v3/rooms/{roomId}/send/m.room.message/{txnId}` with `msgtype: m.text`. Long messages are chunked to 4000 characters per event.
- **Receiving**: Long-polls `GET /_matrix/client/v3/sync` with a 30-second timeout. The first sync discards historical events and records the `next_batch` cursor; subsequent syncs return only new events.
- **Filtering**: Only `m.room.message` events with `msgtype: m.text` are surfaced. Messages sent by the bot's own user ID are ignored so it doesn't reply to itself.

## Troubleshooting

### "Missing required settings"

All four values (`homeserver`, `access_token`, `user_id`, `room_id`) must be set — either under `messaging.matrix` in `instance/config.yaml` or via the corresponding `KOAN_MATRIX_*` env vars.

### `[matrix] API error 401` / `403`

- The access token is invalid or has been revoked. Generate a new one (Step 2).
- The bot account isn't joined to the room. Accept the invite first.

### `[matrix] API error 404`

- The room ID is wrong, or the homeserver doesn't know about it.
- Ensure the room ID starts with `!` and includes the homeserver suffix (e.g., `!abc:matrix.org`).

### Bot replies to its own messages

- Double-check `KOAN_MATRIX_USER_ID` exactly matches the bot's user ID (including the leading `@` and the homeserver part).

### Encrypted rooms

This integration uses unencrypted Matrix rooms. End-to-end encryption (Olm/Megolm) is not implemented — using an E2EE room means messages will appear as undecryptable events. Either disable encryption on the room or create a fresh unencrypted room for the bot.
15 changes: 15 additions & 0 deletions docs/user-manual.md
Original file line number Diff line number Diff line change
Expand Up @@ -1447,6 +1447,21 @@ Each finding becomes a GitHub issue with:
- **Suggested Fix** — Concrete remediation steps
- **Details table** — Severity, category, location, and effort estimate

**Private Vulnerability Reporting (PVRS):** When the target repository has GitHub's Private Vulnerability Reporting enabled, critical and high severity findings are automatically submitted as private security advisories instead of public issues. This prevents disclosure of exploitable vulnerabilities before a fix is applied. Lower-severity findings still create public issues.

Configure PVRS behavior per-project in `projects.yaml`:

```yaml
defaults:
security:
pvrs: auto # auto (detect), true (force), false (public only)
pvrs_threshold: high # minimum severity for PVRS (critical, high, medium, low)
projects:
myapp:
security:
pvrs: false # always use public issues for this project
```

### Incident Triage

**`/incident`** — Triage a production error from a stack trace or log snippet. Kōan will parse the error, identify the root cause, propose a fix with tests, and submit a draft PR.
Expand Down
Loading
Loading