Architecture
The layering
Section titled “The layering”Every arrow is a process or crate boundary that can be tested on its own:
Telegram / Web / CLI / APIs | ka agent runtime | memory + task scheduler + plugin host | stable Ka interface (KaPort) | Ka engineIn the MVP, concretely:
Telegram Bot API ──long poll──┐ local admin CLI ──unix socket──┐ ▼ ▼ ┌──────── ka-agent daemon ──────────────────────┐ │ channels: telegram adapter, admin socket │ │ runtime: session manager, durable queue, │ │ approvals, worker supervisor, budgets, audit │ │ state + search (SQLite) · KaPort │ └───────────────────┬───────────────────────────┘ ▼ supervised worker process ka engine (one per session)The crates
Section titled “The crates”| Crate | Role |
|---|---|
ka-agent-core |
Domain types and state machines: ids, sessions/turns, approvals, tasks, queue, outbox, budgets, audit, strict ka-agent.toml config. No I/O. |
ka-agent-store |
SQLite persistence: schema, migrations, audit chain. |
ka-agent-port |
The KaPort trait and the crate-backed worker boundary. The only crate besides the binary that may depend on ka-*. |
ka-agent-runtime |
Session manager, durable queue, single-flight submission, approvals with deny-on-expiry, budgets, worker supervisor with backoff and a crash-loop breaker. |
ka-agent-channels |
The Telegram Bot API channel and the secret store (env + 0600-file tiers). |
apps/ka-agent |
The binary: run, status, chat, doctor, and the hidden __worker. |
A CI gate (scripts/check-ka-deps.sh) keeps every ka-* dependency inside ka-agent-port, so swapping the boundary implementation stays a one-crate change.
The engine boundary
Section titled “The engine boundary”ka agent talks to engines only through KaPort:
- Forwarded commands:
prompt,answer,abort,set_mode,set_model,set_name,compact. Nothing else. - Never reachable from a channel:
shell,set_feature,set_sandbox,save_api_key,save_settings. The port API has no operation that maps to them; the invariant is enforced by construction and by contract tests. - No “always” from remote channels. Ka’s “always allow” is a coarse per-tool-name write into the project
.ka/ka.toml. Durable grants live in ka agent’s own rules layer instead.
The worker applies a security overlay over any engine configuration, and the overlay always wins:
mode = "guarded" # sessions stay guarded[tools.bash]background_after_ms = 0 # bash jobs never detach (kill reaches them)[features]disable = ["tool:remember"] # memory goes through ka agent# [guards] spend_usd comes from the daemon budgetsWorkers run isolated from your interactive Ka state: their own KA_DATA_DIR, XDG_STATE_HOME, XDG_CONFIG_HOME and HOME, so the operator’s ka.toml, strands, skills and trust store are never shared.
Worker supervision
Section titled “Worker supervision”- Workers are separate processes, one per active session, spawned and stopped by the runtime.
- Crashes respawn with exponential backoff (1s to 60s, configurable) and a crash-loop breaker: too many crashes inside the window degrade the session, and resuming then needs an explicit
/resumeor/new. - Stopping escalates:
abort, wait the grace period (default 10s), then SIGTERM to the worker’s process group (so tool children die too), then SIGKILL. - An engine channel that closes without a terminal
idleis treated as a crash: the turn is marked interrupted, you are notified, and nothing is auto-replayed.
Full contracts: docs/contract/ka-port.md and docs/contract/worker-protocol.md in the repository.