Kerux
Kerux (Greek: κῆρυξ — herald, messenger of the gods) is a fast, self-contained AI agent runtime written in Rust.
It runs a full ReAct agent loop with tool execution, streaming responses, persistent memory, and multi-platform messaging gateways (Telegram, WhatsApp) — all in a single static binary with zero runtime dependencies.
Why Kerux?
- Single binary — no Python, no Node, no runtime.
cargo buildand run. - Fast — Rust core, ~30K LOC, sub-second startup.
- Self-contained — sessions, memory, todos, cron jobs all persist to disk as JSON.
- Multi-platform — Telegram long-polling + WhatsApp (Baileys bridge) adapters built in.
- Production features — tool approval gates, context compaction, fallback provider chains, voice STT, cron scheduling, subagent delegation.
Crate Layout
| Crate | Description |
|---|---|
kerux-core | Agent loop, LLM clients, tools, gateway adapters, persistence |
kerux-cli | CLI/TUI frontend, serve gateway mode, autonomous coding mode |
Attribution
Kerux began as a Rust port of hermes-agent by Nous Research. See ATTRIBUTION.md for details.
Quickstart
Build
git clone https://github.com/eikarna/hermes-rs.git
cd hermes-rs
cargo build --release
The binary lands at target/release/kerux.
Configure
Copy the example config and fill in your provider:
cp kerux.example.toml ~/.config/kerux/kerux.toml
Minimal config:
[client]
provider = "openai"
model = "gpt-4o"
[client.auth]
api_key_env = "OPENAI_API_KEY"
Or use environment variables directly:
export KERUX_PROVIDER=openai
export KERUX_MODEL=gpt-4o
export OPENAI_API_KEY=***
Run
Interactive TUI:
kerux
Single-shot:
kerux run "explain this codebase"
Gateway mode (Telegram + WhatsApp):
kerux serve
Verify
cargo fmt --all
cargo test --workspace
cargo clippy --workspace --all-targets --all-features -- -D warnings
Configuration
Kerux is TOML-first. Config resolution order:
--config <path>CLI flagKERUX_HOMEenvironment variable~/.config/kerux/kerux.toml(Unix) /%APPDATA%\kerux\kerux.toml(Windows)./kerux.tomlin the current directory
See kerux.example.toml for the full annotated reference.
Key Sections
[client]
LLM provider settings: provider, model, base_url, timeout_secs, stream.
[gateway]
Messaging gateway settings:
| Key | Default | Description |
|---|---|---|
telegram_enabled | false | Enable Telegram long-polling adapter |
telegram_token | — | Bot token |
whatsapp_enabled | false | Enable WhatsApp adapter (Baileys bridge) |
whatsapp_bridge_url | http://127.0.0.1:3000 | Bridge endpoint |
streaming_replies | false | Live-edit token streaming with ▌ cursor |
tool_approval | false | Require Telegram inline-keyboard approval before tool execution |
context_compaction | false | Summarize oldest messages near context cap |
stt_model | — | Voice note transcription model (enables STT) |
[delegation]
Subagent delegation settings: provider, model, max_concurrent (default 3).
[fallback]
Fallback provider chain (default OFF): ordered list of providers tried on transient failure.
[autonomous]
Autonomous coding mode: todo_path (default TODO.md), status_path (default autonomous-status.toml), test command, git remote/branch.
Environment Variables
All config fields can be overridden via KERUX_* env vars (e.g. KERUX_PROVIDER, KERUX_MODEL, KERUX_HOME, KERUX_LOG_LEVEL, KERUX_SKILLS_DIR).
Architecture Overview
┌─────────────────────────────────────────────────────┐
│ kerux-cli │
│ TUI (ratatui) │ serve (gateway) │ autonomous mode │
└────────────────────────┬────────────────────────────┘
│
┌────────────────────────▼────────────────────────────┐
│ kerux-core │
│ │
│ ┌──────────┐ ┌──────────┐ ┌───────────────────┐ │
│ │ Agent │ │ Client │ │ Gateway │ │
│ │ (ReAct) │ │ (LLM) │ │ Telegram│WhatsApp │ │
│ └────┬─────┘ └──────────┘ └───────────────────┘ │
│ │ │
│ ┌────▼─────────────────────────────────────────┐ │
│ │ Tools: file, patch, terminal, code_exec, │ │
│ │ web, memory, todo, sub_agent, mcp, skills │ │
│ └──────────────────────────────────────────────┘ │
│ │
│ ┌──────────────────────────────────────────────┐ │
│ │ Persistence: sessions, memory, todos, cron │ │
│ │ (~/.kerux/ — atomic JSON writes) │ │
│ └──────────────────────────────────────────────┘ │
└──────────────────────────────────────────────────────┘
Core Subsystems
| Module | Responsibility |
|---|---|
agent.rs | ReAct loop, streaming, cooperative cancellation, approval gate, context compaction |
client.rs | LLM provider abstraction (OpenAI-compatible, Anthropic, Gemini), fallback chain |
gateway.rs | Platform adapters (Telegram long-polling, WhatsApp bridge), markdown conversion, message chunking |
session_store.rs | Per-channel conversation persistence (format v2 with summary) |
persist.rs | Shared atomic JSON write helpers (~/.kerux/) |
approval.rs | Tool approval gate (inline keyboard via Telegram callback_query) |
scheduler.rs | Cron-style job scheduler with disk persistence |
tools/ | Built-in tool implementations |
platform.rs | OS paths (kerux_home(), config/data/sessions dirs) |
Agent Loop
The core execution engine lives in kerux-core/src/agent.rs.
ReAct Cycle
- Build context — system prompt + conversation history (+
[CONTEXT SUMMARY]if compaction active) - Call LLM — streaming or non-streaming via the configured provider
- Parse response — text chunks, reasoning, tool calls (tolerant parsing)
- Execute tools — with optional approval gate (F1); results appended as tool messages
- Loop — repeat until the model produces a final text response with no tool calls
Cooperative Cancellation
Every iteration checks an Arc<AtomicBool> cancel flag — at loop boundaries, between SSE chunks, and before each tool execution. On cancel, repair_conversation_after_cancel() fixes any dangling assistant/tool message pairs so the conversation stays valid.
Event Emission
The agent emits events (ToolStart, ToolEnd, TextChunk, RunProgress) via a bounded channel using non-blocking try_send() — a slow or dead event pump can never deadlock the ReAct loop.
Context Compaction (F2)
When the conversation approaches the session cap, compact_history() summarizes the oldest messages via a one-shot LLM chat. The summary is stored in the session file (format v2) and injected as a [CONTEXT SUMMARY] marker, keeping recent messages intact.
Gateway & Adapters
The gateway (kerux-core/src/gateway.rs) connects the agent to messaging platforms. Started via kerux serve.
Design
- Long-polling, not webhooks — no HTTP server dependency (YAGNI). Telegram uses
getUpdateswith 30s timeout. - Parallel adapters — each adapter runs in its own
tokio::spawntask so Telegram’s long-poll never starves WhatsApp polling. - Interrupt-on-new-message — an incoming message cancels the active run for that channel and starts a fresh one.
Telegram Adapter
- Markdown → MarkdownV2 conversion (stdlib, custom converter): special-char escaping, fenced code blocks,
---→ Unicode separator, tables → bullet lists, blockquotes - Message chunking at 3500 chars on line boundaries with code-fence tracking
- Live message editing for status updates (
🤔 Thinking...,🔧 Tool, heartbeats) - Two reply modes: normal (final edit) and streaming (
streaming_replies = true, token streaming with▌cursor, ~900ms throttle) - Inline-keyboard tool approval via
callback_query - Voice note STT via
getFile→ download →/v1/audio/transcriptions - Explicit HTTP timeouts (connect 5s, read 45s) — no half-open hangs
WhatsApp Adapter
- Talks to a Baileys HTTP bridge (default
http://127.0.0.1:3000) - Polls
GET /messages(drain-queue), sends viaPOST /send - Custom markdown converter (
markdown_to_whatsapp) — no regex lookarounds - Explicit HTTP timeouts (connect 5s, read 30s)
Persistence
All channel state survives restarts:
| Data | Location |
|---|---|
| Conversations | ~/.kerux/sessions/<platform>_<channel>.json (format v2) |
| Memory | ~/.kerux/memory/memories.json |
| Todos | ~/.kerux/todos/todos.json |
| Cron jobs | ~/.kerux/cron/jobs.json |
All writes are atomic (temp file + rename).
OAuth and provider authentication design
Goal
Add provider authentication that can eventually support official browser login flows while keeping the current OpenAI-compatible API-key path intact.
This is a design checkpoint, not a runtime behavior change.
Provider reality check
- OpenAI: public OpenAI-compatible API access continues to support API keys. OpenAI also documents ChatGPT/Codex auth for Codex clients, including browser login, device/headless login, access-token injection, and auth-cache reuse. Kerux should model this as a separate OpenAI/Codex account-auth capability, not silently treat Codex tokens as generic OpenAI API keys.
- Google: Gemini supports API keys and OAuth/Application Default Credentials. Direct desktop OAuth requires a Google OAuth client ID; ADC via
gcloud auth application-default loginkeeps token creation, refresh, and storage outside Kerux. - GitHub Copilot: official Copilot CLI authentication supports OAuth device flow, supported GitHub token types (
COPILOT_GITHUB_TOKEN,GH_TOKEN,GITHUB_TOKEN), OS keychain storage, and GitHub CLI fallback. Kerux should reference external tokens first and only run Copilot login after provider-specific client behavior is defined. - Anthropic: Claude access is not one single OAuth path. Documented routes include Claude.ai / Claude Code account login, Anthropic Console API keys, Team/Enterprise accounts, and cloud-provider routes such as Google Vertex AI, Amazon Bedrock, and Microsoft Foundry. Kerux should keep these as separate capabilities because Vertex/Bedrock/Foundry require provider-specific request/auth behavior, not just a bearer token swap.
- OpenCode comparison: OpenCode stores provider credentials outside project config and exposes
/connectflows. Kerux should copy the credential separation pattern, not vendor-private auth internals.
Recommended architecture
1. Keep project config non-secret
kerux.toml should continue to describe provider selection, base URLs, and model defaults. It should not become the default storage location for OAuth access tokens or refresh tokens.
Recommended future config shape:
[client]
provider = "openai-compatible"
base_url = "https://api.openai.com/v1"
auth_ref = "openai-default"
auth_ref points to an entry in local credential storage.
2. Store credential metadata locally; store secrets safely
Recommended metadata path:
- Windows:
%APPDATA%/kerux/auth.json - macOS:
~/Library/Application Support/kerux/auth.json - Linux:
~/.local/share/kerux/auth.jsonor config-dir equivalent from existing platform helpers
Rules:
- Never write tokens to repo-local files.
- Never log token values.
- Bind credentials to the endpoint stored in the auth profile, and reject repo-local base URL overrides when an
auth_refis active. - Require explicit base URLs for non-OpenAI profiles until provider-specific clients own their official endpoints.
- Prefer OS credential storage for long-lived secrets and refresh tokens.
- Recommended implementation: use platform credential storage (Windows Credential Manager, macOS Keychain, Linux Secret Service/libsecret) behind a small Kerux abstraction before persisting OAuth refresh tokens. Until that exists, keep tokens in environment variables or provider-managed stores such as Google ADC / GitHub CLI or Copilot CLI keychain.
- If OS credential storage is not implemented yet, keep long-lived secrets in environment variables or explicit config only; do not silently migrate them into plaintext JSON.
- If a plaintext fallback is ever added, it must be opt-in, clearly warned, and protected by best-effort owner-only file permissions.
- Store non-secret provider id, auth type, created/updated timestamps, expiry, and refresh metadata in
auth.json.
Sketch:
{
"version": 1,
"profiles": {
"openai-default": {
"provider": "openai",
"method": "api_key",
"base_url": "https://api.openai.com/v1",
"secret_ref": "env:OPENAI_API_KEY"
},
"google-default": {
"provider": "google-gemini",
"method": "oauth_pkce",
"scopes": ["provider-documented scopes for this flow"],
"expires_at": "2026-01-01T00:00:00Z"
}
}
}
3. Add an auth provider boundary
Introduce a small internal provider-auth abstraction before adding provider-specific flows.
#![allow(unused)]
fn main() {
trait AuthProvider {
fn id(&self) -> &'static str;
fn supported_methods(&self) -> &'static [AuthMethod];
async fn resolve_headers(&self, profile: &AuthProfile) -> Result<HeaderMap>;
}
}
Initial implementations should be minimal:
ApiKeyAuthProviderfor the current OpenAI-compatible behavior.BearerTokenAuthProviderfor official OAuth/ADC access tokens where the provider accepts bearer tokens.
Provider-specific request formats should stay separate from auth. Kerux currently has an OpenAI-compatible client; OAuth should not imply that every provider can use /v1/chat/completions.
4. CLI/TUI flows
Future commands:
kerux auth login <provider>kerux auth set-api-key <provider>kerux auth set-bearer-token <provider> --env <ENV_VAR> --base-url <URL>kerux auth providerskerux auth listkerux auth logout <auth-ref>- TUI command/modal equivalent after CLI flow is stable
Login flow order:
- Prefer API key where it is the official provider API path.
- Prefer provider-managed credentials first: Google ADC, GitHub/Copilot CLI keychain, Claude Code setup wizards, AWS/GCP/Foundry credential chains.
- Offer OAuth only for providers with documented third-party, device-code, or ADC flows.
- Use loopback PKCE for native desktop OAuth where supported.
- Support no-browser mode only through provider-documented flows such as device-code auth or external tools like
gcloud auth application-default login --no-browser; do not invent copy/paste auth-code handling.
5. OAuth implementation constraints
Do not add OAuth until these are decided:
- Token storage format and permission model.
- Provider allowlist and scopes.
- Refresh behavior and expiry handling.
- How non-OpenAI-compatible providers map into
OpenAIClientor a new client abstraction. - How product-specific account auth maps to runtime endpoints, especially OpenAI Codex/ChatGPT auth and Anthropic Claude account auth.
- Whether adding OAuth crates is acceptable, or whether to implement PKCE/loopback using existing dependencies.
- Whether the project will use OS credential storage crates or keep OAuth behind external helper tools until secure storage exists.
Security requirements:
- Use PKCE for public/native clients.
- Bind redirect to loopback only (
127.0.0.1), random port. - Validate
stateand provider issuer/token endpoint. - Loopback callbacks must accept only authorization codes plus validated
state; never accept access tokens from query strings. - Redact auth headers in logs.
Phased implementation plan
Phase 1: auth profiles, no OAuth
- Add local auth metadata store module.
- Add
kerux auth set-api-key <provider>to create a profile that references an environment variable or explicitly configured key source; do not silently persist the secret itself. - Move current API-key resolution behind auth profile lookup while preserving env/config behavior and precedence.
- Add
kerux auth listandkerux auth logout. - Tests: redacted list output, env precedence, missing-secret error, permission best-effort for metadata file.
Phase 2: Google OAuth / ADC-compatible bearer auth
- Add Google as the first official OAuth-capable provider.
- Support existing ADC token discovery or explicit token helper before implementing full browser flow.
- Use provider-documented scopes per Gemini API vs Vertex AI flow; do not hardcode a single scope globally.
- Tests: expired token rejection/refresh boundary with mocked token provider.
Implemented Phase 2a:
kerux auth set-bearer-token <provider> --env <ENV_VAR> --base-url <URL>stores metadata for externally managed OAuth/ADC bearer tokens.- Kerux still does not run browser OAuth or refresh tokens itself.
- Bearer credentials use the same endpoint binding protections as API-key profiles.
Implemented Phase 2b:
kerux auth providersreports provider aliases, documented auth methods, Kerux-supported environment sources, and implementation notes for Google, GitHub Copilot, OpenAI, and Anthropic.- OpenAI Codex/ChatGPT auth and Anthropic Claude account/cloud-provider auth are documented as distinct capabilities instead of being collapsed into generic API-key or bearer-token auth.
kerux auth login <provider>prints provider-specific external setup guidance and intentionally fails without creating credentials until secure token storage and provider-specific runtime clients are available.
Phase 3: browser PKCE flow
- Add loopback OAuth helper.
- Add no-browser flow only for providers with a documented device-code or external-tool path.
- Tests: state validation, callback parsing, token exchange mock server, cleanup of local listener.
Implemented Phase 3a:
- Added provider-neutral PKCE/state helpers.
- Authorization URLs require
http://127.0.0.1:<port>/...loopback redirects. - Callback parsing accepts only authorization codes with matching
stateand rejects access tokens in query strings or fragments. - Added a loopback callback receiver that binds only to
127.0.0.1on a random local port and accepts one GET callback. - Added provider-neutral authorization-code token exchange helper for PKCE flows. Token endpoints must use HTTPS; tests use loopback HTTP only through private test plumbing.
- Kerux still does not launch browsers or refresh/store OAuth tokens itself.
Phase 4: provider-specific clients
- Add provider client abstraction only when the first non-OpenAI-compatible provider needs it.
- Keep OpenAI-compatible behavior unchanged.
Non-goals for the first OAuth PR
- Reverse-engineered ChatGPT/Codex login beyond documented OpenAI flows.
- Reusing Claude Code private credential formats without documented support.
- Storing tokens in repo-local
kerux.toml. - Supporting every provider in one change.
Tool Approval (F1)
Interactive approval gate for tool execution via Telegram inline keyboards.
How It Works
- Agent wants to execute a tool
- Gateway sends a prompt with
[✅ Approve] [❌ Deny]inline buttons - User taps a button → Telegram sends a
callback_query - The query is routed to a per-tool-call
oneshotchannel - Agent proceeds (approve) or skips with a denial message (deny)
- Timeout → treated as denial
Config
[gateway]
tool_approval = true
Implementation
kerux-core/src/approval.rs— approval gate managercallback_queryhandling inTelegramAdapter::poll_updatesanswerCallbackQueryack so the button press registers in the Telegram UI
Context Compaction (F2)
Rolling summarization of old conversation turns to stay within context limits.
How It Works
- After each run, check conversation length against the session cap
- If near cap,
compact_history()sends the oldest N messages to the LLM as a one-shot summarization chat - The summary replaces those messages, embedded as a
[CONTEXT SUMMARY]marker - Recent messages stay intact — only the tail is compressed
Session Format v2
Compaction summaries persist across restarts via the session file format v2:
{
"version": 2,
"summary": "User asked about X; we decided Y...",
"messages": [...]
}
Format v1 (bare message array) is still readable — backward compatible.
Config
[gateway]
context_compaction = true
Fallback Provider Chain (F3)
Automatic failover to backup LLM providers on transient errors.
How It Works
FallbackChainProvider wraps a primary provider plus an ordered fallback list. On each request:
- Try the current provider
- If the error is transient (network failure, 429 rate limit, 5xx, interrupted stream) → advance to the next provider
- Non-transient errors (auth failure, bad request) propagate immediately — no pointless retries
Config
Default OFF (empty fallback list = primary only). Enable explicitly:
[fallback]
enabled = true
[[fallback.providers]]
provider = "openrouter"
model = "anthropic/claude-sonnet-4"
[[fallback.providers]]
provider = "gemini"
model = "gemini-2.5-pro"
Implementation
kerux-core/src/client/fallback.rs—FallbackChainProvider+FallbackEntry+is_transient()detection- Wired in
kerux-cliviawrap_with_fallbacks()around the runtime client
Voice Note STT (F4)
Telegram voice notes are transcribed to text before hitting the agent.
How It Works
- Incoming message with a
voiceattachment detected getFileAPI → download the.ogaaudio via the bot token- POST multipart to
/v1/audio/transcriptions(OpenAI-compatible endpoint, same credentials as the primary client) - Transcript injected as the user message text
Config
OFF unless stt_model is set:
[gateway]
stt_model = "gemini/gemini-2.5-flash"
Any model the provider supports on the transcriptions endpoint works.
Cron Scheduler (F5)
Recurring jobs that fire agent prompts on an interval.
Commands
/cron add <name> <interval> <prompt> — schedule a job
/cron list — show all jobs
/cron pause <name> — pause
/cron resume <name> — resume
/cron remove <name> — delete
Interval Syntax
Stdlib-parsed durations: 30m, 2h, 1d, 1h30m.
Behavior
- Jobs persist to
~/.kerux/cron/jobs.json(atomic writes) - Background ticker in
Gateway::run()checks due jobs each tick - Downtime burst protection: if multiple fires were missed while the process was down, only ONE catch-up run fires
- Job output is delivered to the channel that created it
Implementation
kerux-core/src/scheduler.rs—Schedulerwith atomic JSON persistence
Subagent Delegation (F6)
The agent can delegate focused tasks to isolated child agents.
How It Works
SubAgentTool registers a delegate_to_sub_agent tool. When invoked:
- A child
KeruxAgentis spawned with a fresh conversation (no parent history) - The child runs its own ReAct loop and returns a final summary
- Only the summary enters the parent conversation — intermediate noise stays out
Guardrails
| Guardrail | Value |
|---|---|
| Default | ON |
| Max concurrent children | 3 (tokio semaphore) |
| Nesting depth | 1 (child registry is empty — children cannot delegate further) |
Config
[delegation]
provider = "openai" # optional: separate provider for children
model = "gpt-4o-mini" # optional: cheaper model for delegation
max_concurrent = 3
Implementation
kerux-core/src/tools/sub_agent_tool.rs
Integration Plan: Aider + Kerux
Status: SHIPPED (Phases 1-5 complete as of 0.1.3 main; deltas vs. spec below)
Created: 2026-08-07
Shipped Deltas vs. Plan
Phase-by-phase reality check against the original scope:
- Phase 1 (provider routing): shipped with native OpenAI, Anthropic, Ollama, OpenRouter, and Gemini adapters, plus per-model capability tables with longest-prefix matching (
lookup_capabilities),supports_vision/supports_tool_callsfields, and<edit_format>prompt hints routed from advertised capabilities. - Phase 2 (repo map): shipped (tree-sitter C/Python/Rust/TypeScript, personalized PageRank, token-budgeted renderer). Discovery is capped at 500 files before ranking (
discover_source_files_with_limit). Format follows Aider’s outline style; incremental file-watcher ranking not ported. - Phase 3 (edit blocks): shipped (
edit_blocktool, atomic multi-edit, exact+fuzzy sharingpatchmatching, parsed viaparse_edit_blocks; model-level routing viaEditFormat::SearchReplace/Patch). - Phase 4 (git harness): shipped (
kerux_core::githarnesswith snapshot/guard/commit/undo; TUI/undo).commit_transactionruns per tick in autonomous mode and post-run in the TUI when[agent].auto_commit = true. - Phase 5 (skill & memory lifecycle): shipped as
[curator]policy + non-blocking pass on startup/tick; option[curator].interval_secsenables periodic mid-session passes. Memory pinning (pinned: true) exempts from decay/prune/dedup and is a serialized MEMORY.md header field. Skill staleness keyed off SKILL.md mtime, not usage telemetry. Skill distillation creates tag-clustereddistilled-<tag>drafts;[curator].skill_distill_llm_summary = truerewrites the body via the active LLM (falls back to bullet list on error). Drafts route to_pending/by default ([curator].auto_approve_skills = false) and require TUI approval (a) before loading. Session archiving is idle-time-only. Trajectory compression folds aging low-importance facts into onesession_summaryper pass ([curator].compression_*; deterministic, no LLM).
Shipped since original plan: skill provenance metadata (SkillOrigin::Agent/User, pinned, use_count, last_activity_at) in SKILL.md front matter, with provenance-gated auto-archive in the curator.
Executive Summary
Integrate Aider’s three core capabilities into Kerux to bridge model-agnostic LLM access, intelligent repo indexing, and robust git-driven development workflows:
- Repo Map — AST-based file ranking + Personalized PageRank for token-efficient codebase context
- Edit Format — SEARCH/REPLACE blocks for lean code generation (vs. full-file rewrites)
- Git Integration — Auto-commit with Conventional Commit messages +
/undorollback command
Outcome: Kerux gains Aider’s productivity patterns (model-agnostic backend, edit-efficient workflows) while retaining Hermes-Agent’s self-learning loop (skills, memory, curator, cron).
Current State Analysis
Kerux (Rust, v0.1.3)
Strengths:
- Streaming-first ReAct loop with tolerant XML parsing
- Self-healing LLM error recovery
- TUI + autonomous modes with state persistence
- 99+ unit & integration tests
Gaps:
- LLM client hardcoded to OpenAI (no Anthropic, Ollama, OpenRouter)
- File context is naive (full reads or line-offset pagination)
- Edit tools: only
patchandfile_write(no efficient edit format)
Memory/Skills: Basic memory.rs with no curator, no trajectory compression, no skill lifecycle.
Hermes-Agent (Python reference, ~12k LOC run_agent.py)
Strengths:
- Self-learning: curator + skill auto-creation
- Memory: FTS5 session search with LLM summarization
- Cron/webhook automation + multi-platform gateway
- Durable skill + memory state across sessions
Not for direct reuse: Monolithic codebase; valuable as architectural reference only.
Aider (Python CLI, paul-gauthier/aider)
Strengths:
- Repo map: tree-sitter AST + Personalized PageRank ranking
- Edit format: SEARCH/REPLACE blocks (token-efficient vs. full-file)
- Git harness: dirty-tree protection, Conventional Commit generation,
/undo - Model-agnostic: supports 40+ providers via provider abstraction
Not for adoption: We extract subsystem design, not copy code.
Integration Phases
Phase 1: Model-Agnostic Client (Weeks 1-2)
Goal: Swap hardcoded OpenAI client for pluggable provider abstraction.
Scope:
- Abstract
LLMProvidertrait overOpenAIClient - Implement adapters: OpenAI, Anthropic, Ollama, OpenRouter
- Config extension: provider selection + per-provider settings
- Capability negotiation: max_tokens, edit_format, streaming support
Key Files:
- New:
crates/kerux-core/src/client/provider.rs(trait + routing) - New:
crates/kerux-core/src/client/providers/*.rs(per-provider impl) - Modified:
crates/kerux-core/src/config.rs(provider config section) - Modified:
crates/kerux-core/src/agent.rs(use trait, not hardcoded client)
Design Pattern:
#![allow(unused)]
fn main() {
#[async_trait]
pub trait LLMProvider: Send + Sync {
async fn chat(
&self,
model: &str,
messages: &[Message],
tools: Option<&[ToolSchema]>,
) -> Result<ChatResponse>;
async fn chat_streaming(
&self,
model: &str,
messages: &[Message],
tools: Option<&[ToolSchema]>,
) -> Result<ChatStreamResponse>;
fn capabilities(&self, model: &str) -> ProviderCapabilities;
}
#[derive(Clone)]
pub struct ProviderCapabilities {
pub max_input_tokens: usize,
pub max_output_tokens: usize,
pub edit_format: EditFormat, // search_replace, patch, full_file
pub supports_streaming: bool,
pub supports_reasoning: bool,
pub feature_flags: FeatureFlags,
}
}
Config Extension (TOML):
[client]
provider = "openai" # openai | anthropic | ollama | openrouter
[client.openai]
base_url = "https://api.openai.com/v1"
api_key = "sk-..."
model_mapping = {}
[client.anthropic]
api_key = "sk-ant-..."
model_mapping = {}
[client.ollama]
base_url = "http://localhost:11434"
model_mapping = {}
[client.openrouter]
base_url = "https://openrouter.ai/api/v1"
api_key = "sk-or-..."
model_mapping = {}
Backward Compat: Default to OpenAI if provider not specified; existing kerux.toml files continue working.
Verification:
- All existing tests pass with default (OpenAI) provider
- New provider tests mock HTTP responses (no live API calls)
- CLI invocation still works:
kerux run --query "test"defaults to OpenAI
Phase 2: Repo Map (Weeks 3-4)
Goal: Replace naive file context with Aider-style repo map.
Scope:
- Symbol extraction via tree-sitter (defs + refs per file)
- Personalized PageRank scorer (file importance ranking)
- Token-budget binary search (fit ranked tags to context window)
- Render concise file tree with line numbers and key signatures
Key Files:
- New:
crates/kerux-core/src/repomap/extractor.rs(tree-sitter symbol extraction) - New:
crates/kerux-core/src/repomap/scorer.rs(PageRank ranking) - New:
crates/kerux-core/src/repomap/budgeter.rs(token-budget trimming) - New:
crates/kerux-core/src/repomap/mod.rs(public API) - Modified:
crates/kerux-core/src/agent.rs(inject<repo_map>into system prompt) - Modified:
crates/kerux-core/src/config.rs(add[repomap]section)
Algorithm Summary:
-
Extract symbols from all files (tree-sitter AST traversal):
- Tags:
(rel_path, line, name, kind)where kind = def / ref - Language support: Rust, Python, TypeScript, C (priority order)
- Cache to disk (sled KV: key =
(path, mtime)→ serialized tags)
- Tags:
-
Build ranked graph:
- Create directed graph: file → file (edges = identifier references)
- Apply Personalized PageRank with restart bias toward active chat files + mentioned identifiers
- Per-definition score = aggregated incoming edge rank
- Sort tags by descending score
-
Binary-search trim:
- Target: token budget (default 1024, scales 8x if no active files)
- Midpoint candidate: render ranked tags slice, estimate tokens
- Keep reducing until within 15% tolerance
-
Render output:
- Concise tree format: file paths + line numbers + key definitions
- Preserve relative indentation for nested symbols
Phase 3: Edit Format (Weeks 5-6)
Goal: Add SEARCH/REPLACE block parsing and application.
Scope:
- Parser: extract file path, search block, replace block from LLM stream
- Applier: exact match strategy, fuzzy fallback (normalized whitespace), diff-based fuzzy fallback
- Capability routing: inject verbatim format spec into system prompt if model supports SEARCH/REPLACE
- Fallback: legacy patch tool for models without SEARCH/REPLACE support
Key Files:
- New:
crates/kerux-core/src/tools/edit_parser.rs(SEARCH/REPLACE block parser) - New:
crates/kerux-core/src/tools/edit_applier.rs(fuzzy diff applier) - Modified:
crates/kerux-core/src/agent.rs(system prompt format spec injection) - Modified:
crates/kerux-core/src/tools/builtin.rs(register edit tool)
Verbatim Block Spec:
Every *SEARCH/REPLACE block* must use this format:
1. The *FULL* file path alone on a line, verbatim.
2. The opening fence and code language, eg: ```rust
3. The start of search block: <<<<<<< SEARCH
4. A contiguous chunk of lines to search for in the existing source code
5. The dividing line: =======
6. The lines to replace into the source code
7. The end of the replace block: >>>>>>> REPLACE
8. The closing fence: ```
Application Strategy:
- Exact match (first hit): Find literal search string in file content and replace.
- Relative Indentation Strategy: If exact match fails, normalize whitespace per line, locate match, apply replacement preserving file’s indentation.
- Fuzzy Search Fallback: Use
similarcrate diff engine to locate near-match line spans and apply edits. - Validation: Reject empty-to-empty blocks; log error if search block not found in target file.
Verification:
- Unit tests: parser against Aider output formats
- Integration tests: exact and fuzzy replacements across multi-line blocks
- E2E tests: full agent turn with block edits on sample codebase
Phase 4: Git Integration (Weeks 7-8)
Goal: Transactional git workflow with auto-commit and rollback.
Scope:
- Pre-edit dirty tree check (detect unstaged/staged changes before applying edits)
- Post-edit Conventional Commit generation (call weak model with diff summary)
- Subcommand
/undo(revert last agent commit safely) - Integration into autonomous coding loop
Key Files:
- New:
crates/kerux-cli/src/git_harness.rs(git transactions, Conventional Commit generation, undo) - Modified:
crates/kerux-cli/src/autonomous.rs(delegate git operations to git harness) - Modified:
crates/kerux-cli/src/main.rs(register/undosubcommand)
Workflow Sequence:
-
Pre-Edit Check:
- Run
git status --porcelainto detect uncommitted user changes - If dirty and
auto_commit_dirtyenabled: commit dirty files with message"committing dirty files before agent changes"
- Run
-
Edit Application:
- Apply SEARCH/REPLACE or patch edits
- Run validation command (
cargo test --workspace)
-
Post-Validation Auto-Commit:
- If validation passes: stage edited files (
git add <files>) - Invoke weak LLM model (e.g.
gpt-4o-miniorhaiku) withgit diff --cachedto generate Conventional Commit message - Commit:
git commit -m "<message>" - Append commit hash to
autonomous-status.tomlhistory ledger
- If validation passes: stage edited files (
-
Rollback (
/undo):- Check if HEAD commit was created by agent (verify hash in ledger)
- Safety check: reject if commit has multiple parents (merge) or is pushed (
HEAD==origin/<branch>) - Revert:
git checkout HEAD~1 -- <files>+git reset --soft HEAD~1 - Print confirmation:
Removed: <hash> <commit_message>
Verification:
- Unit tests: git status parser, Conventional Commit prompt construction
- Integration tests: git harness on temporary git repository (commit, rollback, dirty-tree protection)
- E2E test: autonomous loop executing multi-step task with rollback on test failure
Phase 5: Skills & Memory Lifecycle (Weeks 9-10)
Goal: Adapt Hermes-Agent self-learning (curator + skill auto-creation) to Rust.
Scope:
- Extend skill metadata (
created_by,usage_count,last_activity,pinned,state) - Curator background task (scan agent-created skills, archive stale skills after threshold)
- Async skill distillation (extract reusable skills from completed agent runs)
- Skill search & memory integration
Key Files:
- New:
crates/kerux-core/src/curator.rs(curator review loop & skill archiving) - Modified:
crates/kerux-core/src/skills.rs(extend skill metadata and lifecycle state) - Modified:
crates/kerux-core/src/distillation.rs(add skill creation pass after run) - Modified:
crates/kerux-cli/src/main.rs(addkerux curatorsubcommands)
Curator Rules:
- Only touch skills with
created_by = "agent"provenance - Never hard-delete; max destructive action is archive (
~/.kerux/skills/.archive/) - Pinned skills (
pinned = true) are exempt from auto-archiving and review passes - Usage tracking: record
use_count,last_activity_atper skill invocation
Verification:
- Unit tests: curator transition rules, skill metadata serialization
- Integration tests: skill creation from mock conversation history
- E2E test: agent creates skill → curator archives after simulated time elapsed
Architectural Invariants
- Zero Prompt Cache Invalidation: System prompt and tool schemas must remain stable during a conversation. Configuration changes take effect on next session.
- Minimal Footprint: No extra dependencies unless stdlib/existing dependencies cannot solve the problem.
- Graceful Fallback: If a model doesn’t support SEARCH/REPLACE, degrade to
patchtool; if repo map fails, degrade to full-file loading. - Git Safety: Never commit unvalidated edits; never undo user-made commits; always check dirty tree state before modifying files.
- Standard Output & Verification: Every phase requires unit tests, integration tests, and explicit verification against
cargo checkandcargo test.
Kerux Development Progress & Handoff Document
Last Updated: August 10, 2024
Current Version: v0.1.3 (main branch)
Status: ✅ All Aider Phases Complete - Stable Release Candidate
📊 Current State Summary
Implemented Features (Phases 1-5 Complete)
| Phase | Feature | Status | Commit Hash | Notes |
|---|---|---|---|---|
| Phase 1 | Model-agnostic provider routing + capability tables | ✅ Done | aa53ed6 | OpenAI, Anthropic, Ollama, OpenRouter adapters; per-model metadata |
| Phase 2 | Tree-sitter AST repo map extraction + PageRank scoring | ✅ Done | b141e7d, bf25a0a | C/Python/Rust/TypeScript support; capped at 500 files to prevent stalls |
| Phase 3 | Aider-style SEARCH/REPLACE (edit_block) + capability routing | ✅ Done | a697180 | Atomic multi-edit tool with exact+fuzzy matching |
| Phase 4 | Transactional git harness | ✅ Done | 2e7a75a | Snapshots, dirty-tree protection, Conventional Commits, /undo command |
| Phase 5 | Skill & memory lifecycle management | ✅ Done | aa53ed6, a309db0, 2a4485a | Curator passes, decay/prune/dedup, distillation, archival, pinning, LLM prose summarization |
Test Coverage
- kerux-core: 235 tests passing (1 pre-existing env failure: MCP stdio requires Python binary)
- kerux-cli: 104 tests passing
- Repomap-specific: 7 tests all passing
- Clippy: Clean (
--all-targets --all-features -- -D warnings)
🎯 Next Steps - Priority Order
Based on TODO.md tracking and natural progression after Aider integration, here are recommended follow-ups in priority order:
High Priority (User Impact)
1. Trajectory Compression (SHIPPED, fact-level)
[curator].compression_min_age_days (default 60), compression_max_importance (90), compression_min_count (5). Deterministic fold of old, low-importance, unpinned fact blocks into a single session_summary per curator pass — no LLM, no token spend. Distilled (importance 90) and pinned facts are exempt. Inter-session message compression was scoped out: MemoryManager persists blocks, not transcripts (see memory.rs — sessions carry metadata only); lifting that ceiling is future work.
2. Auto-commit Wiring (SHIPPED, run-level)
[agent].auto_commit = true auto-commits a successful interactive run’s working-tree changes via GitHarness::commit_transaction (Conventional Commit derived from staged diff). Wired at TUI run completion (tui/app.rs finish_run_if_ready) rather than per-tool call: run-level commits respect /undo as the intermediate rollback and batch all of a run’s edits into one commit. Autonomous mode already committed per tick.
3. Gemini Adapter (SHIPPED)
crates/kerux-core/src/client/gemini.rs: generateContent + streamGenerateContent?alt=sse (one-shot replay through shared SSE parser — true token streaming is next), systemInstruction/contents/functionDeclarations translation, functionCall↔tool_calls mapping. Capability rows for gemini-2.5-pro/flash + generic gemini- fallback in provider.rs; [client.gemini] section + GEMINI_API_KEY / GEMINI_BASE_URL / GEMINI_TIMEOUT_SECS env overrides in config.rs; ProviderKind::Gemini plumbed through resolve_provider_settings, build_provider_for_kind, CLI factory error text. Chose v1beta (current stable surface with function calling + streaming).
Medium Priority (Quality/UX)
4. Skill Approval Flow (SHIPPED)
Distilled drafts default to <skills>/_pending/ ([curator].auto_approve_skills = false), never auto-load, and appear in the TUI Skills panel with a pending badge. a approves (moves to loadable root, refreshes), d discards. Set auto_approve_skills = true to restore immediate load.
5. Per-Model Edit Format Override (SHIPPED)
[agent].edit_format_override (search_replace | patch | full_file) implemented in config.rs + applied at prompt-hint generation in agent.rs; invalid values rejected by TOML parse error via serde.
Low Priority (Nice-to-have)
6. Vision/Image Input Support
Why: Multimodal agents gain significant capabilities (document scanning, screenshot understanding).
Status: ProviderCapabilities added supports_vision field but no implementation exists yet.
Work needed: Minimal framework setup, heavy integration work:
- Image preprocessing pipeline (resize, encode, base64)
- Multimodal request construction per-provider
- Tool-call restrictions (vision-only contexts disable some tools)
ETA: ~8-12 hours per major provider (Anthropic/Ollama first)
🧪 Known Issues & Technical Debt
Pre-existing Environment Failure
- Issue:
mcp::tests::stdio_client_connects_lists_and_calls_toolfails requiringpythonbinary - Impact: None in production use case (MCP stdio just optional feature)
- Fix required: Install Python on test runners or mock python dependency in tests
Performance Edge Cases
- Huge repos (>6k files): Now protected via file capping but discovery still scans entire tree first. Optimization: early-return scan once cap hit.
- Tree-sitter parsing on very large files (>1MB): Parser may stall. Recommendation: implement file size threshold warning in extractor.
Missing Documentation
- TODO: Update AGENTS.md with new features documentation
- TODO: Add usage examples to README for [curator] configuration
- TODO: Write RFC for trajectory compression design before implementation
📁 File Locations Reference
| Component | Primary Files | Purpose |
|---|---|---|
| Repo Map | crates/kerux-core/src/repomap/extractor.rs, scorer.rs, budgeter.rs | Symbol extraction, PageRank ranking, token-budgeted rendering |
| Git Harness | crates/kerux-core/src/githarness.rs, crates/kerux-cli/src/tui/app.rs (/undo) | Snapshot/restore, dirty-tree protection, Conventional Commits |
| Curator | crates/kerux-core/src/curator.rs | Decay/prune/dedup/archive/distillation loops |
| Skills | crates/kerux-core/src/skills.rs, crates/kerux-core/src/repomap/budgeter.rs | SKILL.md front matter loading, metadata persistence, archive management |
| Config | crates/kerux-core/src/config.rs, kerux.example.toml | TOML-based settings runtime config resolution |
| Providers | crates/kerux-core/src/client/anthropic.rs, openai.rs, ollama.rs, openrouter.rs | LLM provider implementations with streaming normalization |
| Agents | crates/kerux-core/src/agent.rs, crates/kerux-cli/src/tui/app.rs | ReAct loop orchestration, system prompt construction |
🚀 Quick Start for New Contributor
-
Clone repository:
git clone https://github.com/yourservice/kerux.git cd kerux -
Install dependencies:
# Rust toolchain (Rustup recommended) rustup update stable && cargo update # Python (for MCP tests only - optional) sudo apt install python3 # Ubuntu/Debian -
Run tests:
cargo test --workspaceExpected: 235 core tests + 104 CLI tests = 339 total (1 known skip/failure)
-
Build binary:
cargo build --release target/release/kerux -
Start coding:
- Pick task from “Next Steps - Priority Order” above
- Read relevant files listed under “What to do”
- Write failing test first (TDD approach)
- Run
cargo clippyduring development - Submit PR referencing this progress doc
📞 Contact Information
Primary Maintainers: [List maintainers here]
Slack Channel: #channel-name
GitHub Issues: [Link to issues page]
Handoff Instructions: When handing off project maintenance or transitioning team members, share this PROGRESS.md along with:
- Full test suite results (
cargo test --workspace) - Recent commit history (
git log --oneline -10) - Current open pull requests (
gh pr list) - Access tokens/secrets rotation schedule
Document generated: 2024-08-10
Repository main branch HEAD: bf25a0a
Branch ahead of origin/main by 10 commits