SPB Git

spb/khaelor

Public

KHAELOR — a terminal-native autonomous engineering agent powered by Anthropic.

feat: initial commit — KHAELOR terminal-native autonomous engineering agent

Simon-Pierre Boucher committed (Aug 10, 2026) Browse files

24 changed files +20,001 −0

added .gitignore +36 −0
@@ -0,0 +1,36 @@
1 +# KHAELOR
2 +# File: .gitignore
3 +# Description: Git ignore rules — secrets, dependencies, build artifacts, reference repos.
4 +#
5 +# Author: Simon-Pierre Boucher
6 +# Contact: contact@spboucher.ai
7 +
8 +# Secrets — never commit
9 +.env
10 +.env.*
11 +*.key
12 +
13 +# Reference implementations (cloned third-party repos, not part of KHAELOR)
14 +references/
15 +
16 +# Dependencies
17 +node_modules/
18 +
19 +# Build output
20 +dist/
21 +build/
22 +*.tsbuildinfo
23 +
24 +# Logs
25 +logs/
26 +*.log
27 +
28 +# OS
29 +.DS_Store
30 +
31 +# Local tooling state
32 +.khaelor/cache/
33 +coverage/
34 +
35 +# npm pack artifacts (published as release assets)
36 +*.tgz
added CLAUDE.md +996 −0
@@ -0,0 +1,996 @@
1 +# KHAELOR — CLAUDE.md
2 +
3 +> **Project:** KHAELOR
4 +> **Command:** `khaelor`
5 +> **Identity:** A terminal-native autonomous engineering agent powered by Anthropic.
6 +> **Internal principle:** Understand first. Design second. Implement third.
7 +> **Author / Maintainer:** Simon-Pierre Boucher — contact@spboucher.ai
8 +
9 +---
10 +
11 +## 1. MISSION
12 +
13 +You are building **KHAELOR**, a next-generation autonomous terminal agent.
14 +
15 +KHAELOR is **not** another Claude Code clone. It must combine the strongest architectural and UX ideas found in:
16 +
17 +- Claude Code
18 +- Hermes Agent
19 +- OpenCode
20 +- OpenHands / OpenHands Software Agent SDK
21 +- mini-SWE-agent (where its minimalism is useful)
22 +
23 +…while developing its **own architecture and identity**.
24 +
25 +**Long-term goal:** build the best terminal-native AI agent interface in existence.
26 +
27 +KHAELOR V1 is **Anthropic-only**. Do NOT implement OpenAI, Gemini, OpenRouter, Ollama, or local models in the first version. The architecture must nevertheless remain clean enough that additional providers could later be added without rewriting the agent kernel.
28 +
29 +The first release must focus obsessively on:
30 +
31 +1. Exceptional terminal UX
32 +2. Excellent Anthropic integration
33 +3. Reliable agent execution
34 +4. Clean architecture
35 +5. Context efficiency
36 +6. Observable actions
37 +7. Fast interaction
38 +8. Safe and understandable permissions
39 +9. Persistent sessions
40 +10. Strong repository intelligence
41 +
42 +---
43 +
44 +## 2. ABSOLUTE RULES (NON-NEGOTIABLE)
45 +
46 +These rules override everything else in this document. Violating any of them means the work is **not done**, regardless of whether it compiles, passes tests, or looks finished.
47 +
48 +### ABSOLUTE RULE #0 — MANDATORY FILE HEADERS
49 +
50 +**Every source code file created or substantially rewritten in this project MUST begin with an author header.**
51 +
52 +Required fields:
53 +
54 +- **Author:** Simon-Pierre Boucher
55 +- **Contact:** contact@spboucher.ai
56 +- **File:** relative path from repository root
57 +- **Description:** one line describing the file's purpose
58 +
59 +#### TypeScript / JavaScript (`.ts`, `.tsx`, `.js`, `.mjs`, `.cjs`)
60 +
61 +```ts
62 +/**
63 + * KHAELOR
64 + * File: src/agent/kernel.ts
65 + * Description: Minimal agent kernel — coordinates context, model, and tool execution.
66 + *
67 + * Author: Simon-Pierre Boucher
68 + * Contact: contact@spboucher.ai
69 + */
70 +```
71 +
72 +#### Shell scripts (`.sh`)
73 +
74 +```bash
75 +#!/usr/bin/env bash
76 +# KHAELOR
77 +# File: scripts/build.sh
78 +# Description: Production build script.
79 +#
80 +# Author: Simon-Pierre Boucher
81 +# Contact: contact@spboucher.ai
82 +```
83 +
84 +#### Rust (`.rs`) — if/when a native component exists
85 +
86 +```rust
87 +//! KHAELOR
88 +//! File: native/src/lib.rs
89 +//! Description: Performance-critical native component.
90 +//!
91 +//! Author: Simon-Pierre Boucher
92 +//! Contact: contact@spboucher.ai
93 +```
94 +
95 +#### Markdown documentation (`.md`) — when authored as a deliverable
96 +
97 +```md
98 +<!--
99 +KHAELOR
100 +File: docs/ARCHITECTURE.md
101 +Author: Simon-Pierre Boucher
102 +Contact: contact@spboucher.ai
103 +-->
104 +```
105 +
106 +#### Exceptions
107 +
108 +- Pure data files that cannot carry comments (`.json`, lockfiles, binary assets) are exempt. Where a config format supports comments (`.jsonc`, `.yaml`, `.toml`), the header IS required.
109 +- Generated files must carry the header in their generator template, plus a `Generated file — do not edit` line.
110 +- Vendored/reference code under `references/` keeps its original authorship and licenses. **Never** apply this header to third-party code.
111 +
112 +#### Enforcement
113 +
114 +- Add a lint/CI check (`scripts/check-headers` or an ESLint rule) that fails the build if any first-party source file is missing the header.
115 +- The header check is part of the definition of done for every phase.
116 +- When editing an existing file that lacks a header, add it.
117 +
118 +### ABSOLUTE RULE #1 — UNDERSTAND BEFORE BUILDING
119 +
120 +Do not begin implementing KHAELOR immediately. The first phase of this project is **mandatory architectural research** (Phase 0, below). No major production implementation may begin before the research documents exist.
121 +
122 +The rule is:
123 +
124 +```
125 +UNDERSTAND FIRST → DESIGN SECOND → IMPLEMENT THIRD
126 +```
127 +
128 +### ABSOLUTE RULE #2 — THE TERMINAL IS THE PRODUCT
129 +
130 +The terminal is not merely where KHAELOR runs. The terminal **IS** the product. Any feature that technically works but is not the best terminal interaction we can design is **not finished**.
131 +
132 +### ABSOLUTE RULE #3 — SMALL KERNEL
133 +
134 +The agent kernel stays minimal. Complexity lives in services **around** the kernel, never inside it. Whenever tempted to add something to `AgentKernel`, ask: *"Can this be a service around the kernel instead?"* The answer should almost always be yes.
135 +
136 +### ABSOLUTE RULE #4 — NEVER FABRICATE
137 +
138 +Never invent token usage, progress percentages, test results, or completion claims. Every displayed number must come from real data. Every completion claim must be backed by evidence (see §14).
139 +
140 +### ABSOLUTE RULE #5 — PROTECT USER WORK
141 +
142 +Never assume an existing diff belongs to KHAELOR. Record baseline git state before edits. Never auto-commit unless explicitly requested. Never destroy uncommitted user changes.
143 +
144 +---
145 +
146 +## 3. PHASE 0 — DEEP REPOSITORY ANALYSIS (MANDATORY)
147 +
148 +Before creating the production architecture, independently inspect the **current source code** of:
149 +
150 +- https://github.com/NousResearch/hermes-agent
151 +- https://github.com/anomalyco/opencode
152 +- https://github.com/OpenHands/OpenHands
153 +- https://github.com/OpenHands/software-agent-sdk
154 +- https://github.com/SWE-agent/mini-swe-agent (where useful)
155 +
156 +Do not merely read READMEs. Clone the repositories into a reference directory **outside** KHAELOR's production source tree:
157 +
158 +```
159 +references/
160 + hermes-agent/
161 + opencode/
162 + openhands/
163 + openhands-agent-sdk/
164 + mini-swe-agent/
165 +```
166 +
167 +These are **reference implementations only**. Do not blindly copy source code. Respect their licenses. The purpose is to understand their engineering decisions and derive a better architecture.
168 +
169 +**Required deliverables before any major implementation:**
170 +
171 +```
172 +docs/research/
173 + HERMES_ANALYSIS.md
174 + OPENCODE_ANALYSIS.md
175 + OPENHANDS_ANALYSIS.md
176 + MINI_SWE_ANALYSIS.md
177 + COMPARATIVE_ARCHITECTURE.md
178 + KHAELOR_ARCHITECTURE_DECISIONS.md
179 +```
180 +
181 +For each repository, trace **real code paths**: follow imports, find the agent loops, the tool registries, the session state, the render loops, the permission checks, context construction, process execution, and persistence.
182 +
183 +### 3.1 Hermes Agent analysis
184 +
185 +Investigate at minimum:
186 +
187 +**Agent loop.** Locate the actual implementation of: user message → context construction → LLM request → tool calls → tool execution → observations → next LLM call. Determine where the central agent class lives; how messages are represented; how tool calls are parsed; how tool outputs enter context; failure handling; retry strategy; cancellation behavior; how task completion is represented.
188 +
189 +**Context management.** Prompt construction; context compression; context-window pressure handling; prompt caching; conversation summarization; memory injection; system prompt construction.
190 +
191 +**Memory.** Persistent memory; session memory; memory providers; memory search; cross-session retrieval; how memories are written and selected.
192 +
193 +**Skills.** Skill format; discovery; loading; automatic creation; improvement; persistence; context injection.
194 +
195 +**Terminal execution.** Local execution; process lifecycle; Docker; SSH; remote execution abstractions; command output; timeouts; async processes.
196 +
197 +**Subagents.** Creation; isolation; context inheritance; result propagation; concurrency; lifecycle; recursion limits.
198 +
199 +**TUI.** Layout; rendering; streaming; keyboard navigation; tool rendering; session selection; model switching; status presentation; long-output handling.
200 +
201 +Record explicitly: WHAT HERMES DOES VERY WELL / WHAT HERMES DOES POORLY / WHAT KHAELOR SHOULD ADOPT / WHAT KHAELOR SHOULD NOT COPY.
202 +
203 +### 3.2 OpenCode analysis
204 +
205 +OpenCode is particularly important for KHAELOR because of its terminal-first design. Perform the same depth of analysis, covering:
206 +
207 +**TUI framework.** Exact terminal UI libraries/frameworks; component architecture; event model; keyboard handling; resizing; scrolling; rendering strategy; markdown rendering; syntax highlighting; input editor; autocomplete; command palette; overlays/modals; diff visualization; streaming output.
208 +
209 +**Agent architecture.** Build agent, plan agent, subagents, sessions, messages, tool calls, permissions. Determine whether modes are different agents, different prompts, permission policies, or a combination.
210 +
211 +**State.** Session persistence; conversation state; project state; working directory handling; configuration; per-project settings.
212 +
213 +**Tools.** Implementations of `bash`, `read`, `write`, `edit`, `grep`, `glob`, `task/subagent`. Study their schemas carefully — especially how OpenCode avoids overwhelming the model with unnecessary tool complexity.
214 +
215 +**Permission UX.** Rules; allow/ask/deny behavior; scope; persistence; TUI confirmation interactions.
216 +
217 +**Performance.** Startup time; rendering strategy; streaming; caching; unnecessary re-renders; long-session performance. Identify what makes OpenCode feel fast even when model inference is not.
218 +
219 +### 3.3 OpenHands analysis
220 +
221 +OpenHands matters most architecturally. Study the separation between:
222 +
223 +| Concern | OpenHands concept |
224 +|---|---|
225 +| Intelligence | Agent |
226 +| Lifecycle | Conversation / Session |
227 +| Environment | Workspace |
228 +| Actions | Tools |
229 +| History | Events / EventLog |
230 +| Safety | Security analyzers / confirmation |
231 +
232 +This separation should heavily influence KHAELOR. Study: local and remote workspaces; sandbox model; file editing; shell execution; event log; persistence; security analyzers; permission confirmation; observation representation. Determine whether KHAELOR can adopt the **principles** without inheriting unnecessary framework complexity.
233 +
234 +### 3.4 mini-SWE-agent analysis
235 +
236 +Study mini-SWE-agent for one question: **How little agent scaffolding is actually necessary?**
237 +
238 +Locate its fundamental loop — conceptually: query model → execute actions → return observations → repeat. KHAELOR must preserve this philosophical minimalism inside its kernel. Complexity lives around the kernel, not inside it.
239 +
240 +### 3.5 Comparative architecture
241 +
242 +Create `docs/research/COMPARATIVE_ARCHITECTURE.md` with a decision matrix:
243 +
244 +| Capability | Hermes | OpenCode | OpenHands | mini-SWE | KHAELOR Decision |
245 +|---|---|---|---|---|---|
246 +| Agent loop | | | | | |
247 +| TUI | | | | | |
248 +| Tools | | | | | |
249 +| Sessions | | | | | |
250 +| Context | | | | | |
251 +| Memory | | | | | |
252 +| Permissions | | | | | |
253 +| Workspace | | | | | |
254 +| Events | | | | | |
255 +| Subagents | | | | | |
256 +| Processes | | | | | |
257 +| Model abstraction | | | | | |
258 +| Persistence | | | | | |
259 +| Git awareness | | | | | |
260 +| Repository search | | | | | |
261 +
262 +For every architectural choice answer: What problem does this solve? How does each reference solve it? What are the trade-offs? What should KHAELOR do differently, and why?
263 +
264 +---
265 +## 4. DESIGN PHILOSOPHY
266 +
267 +KHAELOR should feel like:
268 +
269 +```
270 +Claude Code + OpenCode + Hermes + OpenHands
271 +− accumulated complexity
272 ++ a radically better terminal interface
273 +```
274 +
275 +### Core architectural principle — a small kernel
276 +
277 +Conceptually:
278 +
279 +```python
280 +while session.active:
281 + context = context_engine.build(session.state)
282 + response = model.generate(
283 + context=context,
284 + tools=tool_registry.available(session.state),
285 + )
286 + session.record(response)
287 + observations = executor.execute(response.actions)
288 + session.record(observations)
289 + if response.requests_completion:
290 + verify()
291 +```
292 +
293 +The production implementation will require more sophistication — but the kernel must never become a god object. The kernel **coordinates**; it does not own everything.
294 +
295 +### Target system architecture
296 +
297 +```
298 + KHAELOR TUI
299 + │
300 + ▼
301 + ┌────────────────┐
302 + │ Session Engine │
303 + └───────┬────────┘
304 + │
305 + ▼
306 + ┌────────────────┐
307 + │ Agent Kernel │
308 + └───────┬────────┘
309 + │
310 + ┌──────────────────┼──────────────────┐
311 + │ │ │
312 + ▼ ▼ ▼
313 + Context Engine Tool Runtime Model Runtime
314 + │ │ │
315 + │ │ Anthropic API
316 + ▼ ▼
317 + Repository Index Workspace
318 + │
319 + ┌──────┴──────┐
320 + │ │
321 + Files Process
322 +```
323 +
324 +Future systems (Memory, Skills, Subagents, Browser, MCP, remote execution) must be attachable **without rewriting the kernel** — but they are NOT V1 priorities, and future flexibility is not an excuse for premature abstraction.
325 +
326 +---
327 +
328 +## 5. TECHNOLOGY DECISION
329 +
330 +Determine the final language/runtime **after** Phase 0 — but strongly consider **TypeScript** for: rich terminal UI, async I/O, Anthropic SDK integration, packaging, cross-platform distribution, ecosystem, and OpenCode/OpenTUI learnings.
331 +
332 +- A Rust native component is acceptable **later** for performance-critical pieces. Avoid premature polyglot architecture.
333 +- Ship as one easily installable CLI: `npm install -g khaelor` → `khaelor`.
334 +- **No Electron. No web frontend.** KHAELOR V1 is terminal-native.
335 +
336 +---
337 +
338 +## 6. ANTHROPIC INTEGRATION
339 +
340 +### Anthropic-only, cleanly isolated
341 +
342 +KHAELOR V1 supports Anthropic models only, via the official Anthropic SDK. The model layer must nevertheless be isolated behind a single interface:
343 +
344 +```ts
345 +interface ModelClient {
346 + stream(request: ModelRequest): AsyncIterable<ModelEvent>;
347 +}
348 +```
349 +
350 +Do NOT build a generic provider framework. Do NOT implement other providers. The reason for `ModelClient` is clean architecture, not multi-provider complexity.
351 +
352 +### Configuration
353 +
354 +```
355 +khaelor
356 +khaelor --model <anthropic-model>
357 +```
358 +
359 +In-app: `/model` opens an elegant model selector; `/config` opens configuration.
360 +
361 +Configuration hierarchy (highest precedence first):
362 +
363 +```
364 +CLI flags → project configuration → user configuration → environment → defaults
365 +```
366 +
367 +Files:
368 +
369 +```
370 +~/.khaelor/config.json # user
371 +.khaelor/config.json # project
372 +```
373 +
374 +Environment: `ANTHROPIC_API_KEY`.
375 +
376 +**Security requirements:**
377 +- Never display secrets.
378 +- Never write API keys or authorization headers to logs.
379 +- If an API key is entered interactively, prefer secure OS storage (keychain) where practical.
380 +
381 +### Model configuration
382 +
383 +Do not hard-code a permanent model list — Anthropic model identifiers evolve. Allow configuration such as:
384 +
385 +```json
386 +{
387 + "model": "configured-anthropic-model-id",
388 + "thinking": "adaptive",
389 + "maxOutputTokens": 16000
390 +}
391 +```
392 +
393 +`/model` should show current model, alternatives, thinking mode, and output budget. Model aliases may be supported.
394 +
395 +---
396 +
397 +## 7. STREAMING AND THE EVENT BUS
398 +
399 +### First-class streaming
400 +
401 +Streaming is native to the architecture. Never implement "request → wait → print huge response." Everything is event-driven:
402 +
403 +```
404 +ModelStarted · TextDelta · ThinkingDelta · ToolCallStarted · ToolInputDelta
405 +ToolStarted · ToolOutputDelta · ToolFinished · ModelFinished
406 +```
407 +
408 +The UI consumes events. This is essential.
409 +
410 +### Typed event bus
411 +
412 +Use typed events throughout the system:
413 +
414 +```
415 +SessionStarted · UserMessageCreated · ModelRequestStarted · ModelTextDelta
416 +ModelThinkingDelta · ModelResponseCompleted · ToolRequested · ToolApproved
417 +ToolStarted · ToolOutput · ToolCompleted · ToolFailed · FileRead · FileModified
418 +ProcessStarted · ProcessOutput · ProcessExited · ContextCompacted
419 +PermissionRequested · PermissionGranted · PermissionDenied
420 +TaskCompleted · TaskFailed
421 +```
422 +
423 +Events must be appendable to persistent session history where appropriate. The event log is the source of truth for session replay and resume.
424 +
425 +---
426 +
427 +## 8. SESSION ENGINE
428 +
429 +Agent intelligence and session lifecycle must remain separate:
430 +
431 +```
432 +AgentKernel = reason and act
433 +Session = lifecycle and history
434 +Workspace = world
435 +Tool = action
436 +Model = intelligence backend
437 +```
438 +
439 +Implement **persistent sessions early**.
440 +
441 +Commands: `/sessions` `/resume` `/new` `/rename` `/clear` — eventually `/branch` and `/rewind`.
442 +
443 +Session metadata: `id`, `title`, `project`, `createdAt`, `updatedAt`, `model`, `tokenUsage`, `cost`, `gitBranch`, `workingDirectory`.
444 +
445 +---
446 +
447 +## 9. WORKSPACE
448 +
449 +Create a clean workspace abstraction. V1 requires only `LocalWorkspace`:
450 +
451 +```ts
452 +interface Workspace {
453 + cwd(): string;
454 + readFile(path: string): Promise<string>;
455 + writeFile(path: string, content: string): Promise<void>;
456 + exec(command: Command): Promise<ProcessResult>;
457 +}
458 +```
459 +
460 +Do not implement Docker/SSH yet — but prevent the core agent from depending directly on Node filesystem/process globals everywhere.
461 +
462 +---
463 +
464 +## 10. V1 TOOLS
465 +
466 +Keep the primitive tool set intentionally small. The model should have **powerful primitives**, not dozens of tiny tools.
467 +
468 +V1 tools: `read` · `write` · `edit` · `grep` · `glob` · `bash` · `process` (and potentially `git` after careful evaluation).
469 +
470 +### read
471 +Line ranges; binary detection; file size guards; syntax metadata; efficient large-file handling. Rendered with file path header and line numbers.
472 +
473 +### write
474 +Complete-file writes. Before overwrite: understand existing file state; detect external modification; keep event history. **All new source files must include the mandatory header (Absolute Rule #0).**
475 +
476 +### edit
477 +One of the most important tools — study OpenCode and OpenHands editing implementations carefully. Requirements: exact replacements; robust patch application; useful failure messages; ambiguity detection; line-ending handling; indentation preservation; atomic writes. Provide a diff whenever possible.
478 +
479 +### grep
480 +Fast textual repository search. Prefer native high-performance tools (ripgrep) where available. Return concise structured results. Never dump thousands of lines into model context.
481 +
482 +### glob
483 +Efficient filesystem discovery (`**/*.ts`, `src/**/*.py`) with sensible ignore behavior. Respect `.gitignore` and `.khaelorignore` where appropriate.
484 +
485 +### bash vs process
486 +Shell execution and long-running processes must not be conflated. Short commands → `bash`. Long-running commands → `process`.
487 +
488 +### Process manager
489 +KHAELOR must be materially better than agents that treat every shell command as blocking.
490 +
491 +API: `process.start` · `process.list` · `process.read` · `process.write` · `process.stop`
492 +
493 +```
494 +PROCESSES
495 +● 3121 npm run dev running 04:32
496 +● 3198 pytest running 00:18
497 +○ 3012 npm test exited code 0
498 +```
499 +
500 +The agent can: start a dev server → continue editing → inspect output → run tests → inspect the dev server again.
501 +
502 +---
503 +
504 +## 11. REPOSITORY INTELLIGENCE
505 +
506 +KHAELOR should understand a repository **without stuffing it into model context**. Build an incremental repository index.
507 +
508 +V1: filesystem map · git status · git diff · grep/ripgrep · file metadata · recently accessed files.
509 +
510 +Later (not V1): AST · symbols · references · imports · dependency graph · embeddings.
511 +
512 +Expose repository intelligence exclusively through the Context Engine.
513 +
514 +---
515 +
516 +## 12. CONTEXT ENGINE
517 +
518 +One of KHAELOR's most important components. `ContextEngine` decides what the model actually receives:
519 +
520 +```
521 +SYSTEM identity · behavior · tools · project instructions
522 +WORKING CONTEXT current request · recent turns · current actions · current failures
523 +PROJECT CONTEXT relevant files · git state · repository structure
524 +CHECKPOINT earlier session summary
525 +```
526 +
527 +Do NOT simply append everything forever.
528 +
529 +### Context compaction
530 +
531 +Implement context-pressure awareness. Before context becomes dangerous, create a structured checkpoint:
532 +
533 +```yaml
534 +objective: ...
535 +completed: [...]
536 +current_state: ...
537 +important_files:
538 + - path: ...
539 + reason: ...
540 +changes: [...]
541 +failed_attempts: [...]
542 +decisions: [...]
543 +running_processes: [...]
544 +next_steps: [...]
545 +```
546 +
547 +Preserve important raw evidence when summarization could destroy necessary details. Compaction should feel invisible unless the user asks to inspect it (`/context`, `/compact`).
548 +
549 +### Project instructions
550 +
551 +Automatically discover project-level instructions. Support `KHAELOR.md`, with potential compatibility for `CLAUDE.md` and `AGENTS.md` — but define a clear precedence order:
552 +
553 +```
554 +~/.khaelor/KHAELOR.md → repository/KHAELOR.md → nested/directory/KHAELOR.md
555 +```
556 +
557 +Instructions closer to the current working path refine global instructions.
558 +
559 +---
560 +
561 +## 13. PERMISSION SYSTEM
562 +
563 +Permissions must be powerful but not annoying. Evaluate actions by **capabilities**, not arbitrary tool names:
564 +
565 +```
566 +file.read · file.write · process.execute · network.access · git.modify · filesystem.external
567 +```
568 +
569 +Policy values: `allow` · `ask` · `deny`. User-configurable, e.g.:
570 +
571 +```json
572 +{
573 + "permissions": {
574 + "file.read": "allow",
575 + "file.write.project": "allow",
576 + "process.execute": "allow",
577 + "filesystem.outsideProject": "ask"
578 + }
579 +}
580 +```
581 +
582 +Keep destructive or unusually broad actions visible to the user.
583 +
584 +### Permission UI
585 +
586 +Never show ugly `Allow? y/n` prompts. Use an elegant inline panel:
587 +
588 +```
589 +╭─ KHAELOR requests permission ─────────────────────╮
590 +│ Run │
591 +│ npm install │
592 +│ │
593 +│ Working directory │
594 +│ ~/dev/project │
595 +│ │
596 +│ [ Enter ] Allow once │
597 +│ [ A ] Always allow in this project │
598 +│ [ Esc ] Deny │
599 +╰───────────────────────────────────────────────────╯
600 +```
601 +
602 +The interaction should take milliseconds.
603 +
604 +---
605 +## 14. THE TERMINAL UI
606 +
607 +**This section is non-negotiable.** Do not create a generic `> prompt / AI: response` REPL. KHAELOR should feel like a modern interactive computing environment.
608 +
609 +### UI design principles
610 +
611 +Be: minimal · dense when needed · calm · extremely fast · keyboard-native · beautiful without being decorative · information-rich · predictable.
612 +
613 +Avoid: rainbow colors · excessive borders · ASCII gimmicks · constant animations · emoji everywhere · huge banners · screen flicker.
614 +
615 +Visual hierarchy comes from **spacing, typography, subtle color, indentation, and status** — not noise.
616 +
617 +### Startup experience
618 +
619 +`khaelor` starts nearly instantly:
620 +
621 +```
622 + KHAELOR
623 + ~/dev/my-project · main
624 + Claude · configured model
625 +────────────────────────────────────────────────────
626 + What do you want to build?
627 + ❯ _
628 +```
629 +
630 +No giant ASCII logo. No multi-second animation. No startup log spam.
631 +
632 +### Composer (highest-priority component)
633 +
634 +Features: multiline editing · cursor and word navigation · selection · copy/paste · history · undo/redo if feasible · slash commands · file mentions · fuzzy autocomplete · shell shortcuts · drag/drop paths where the terminal permits · message queueing while the agent works · image attachments later if useful.
635 +
636 +### File mentions
637 +
638 +Typing `@` opens fuzzy repository search:
639 +
640 +```
641 +@agent
642 + src/kernel/agent.ts
643 + src/agents/agent-runtime.ts
644 + tests/agent.test.ts
645 +```
646 +
647 +Selection inserts a **structured file reference**, not necessarily the entire file. Support `@src/kernel/agent.ts`; future syntax `@src/kernel/agent.ts:40-90`.
648 +
649 +### Slash command palette
650 +
651 +Typing `/` opens: `/model` `/config` `/permissions` `/context` `/sessions` `/resume` `/new` `/compact` `/cost` `/status` `/diff` `/processes` `/help` `/quit` — with fuzzy filtering, keyboard navigation, and descriptions.
652 +
653 +### Universal command palette
654 +
655 +`Ctrl+K` opens a universal palette (Change model · View diff · Open sessions · View context · Manage permissions · Show processes · Compact context · New session). Users should not need to memorize commands.
656 +
657 +### Status bar
658 +
659 +Persistent but subtle:
660 +
661 +```
662 + main +4 −1 │ Claude model │ context 31% │ $0.42
663 +```
664 +
665 +Candidates: git branch · dirty files · model · context utilization · session cost · background processes · mode. Do not overload it.
666 +
667 +### Agent states
668 +
669 +The user must always understand what KHAELOR is doing. States: thinking · reading · searching · editing · running · waiting · verifying · idle. Use one compact dynamic status line:
670 +
671 +```
672 +● Searching repository · 2.3s
673 +● Editing src/kernel/agent.ts
674 +● Running tests
675 +```
676 +
677 +Do not spam the conversation with transient status messages.
678 +
679 +### Tool call presentation
680 +
681 +Collapsed by default:
682 +
683 +```
684 +▸ Read src/kernel/agent.ts
685 +▸ Search "ContextEngine" · 14 matches
686 +▸ Edit src/context/engine.ts · +31 −12
687 +▸ Run npm test · passed
688 +```
689 +
690 +Expandable on demand; the user can toggle detail level.
691 +
692 +### Edit presentation and diff viewer
693 +
694 +Never print just "Edited file." Show `✓ src/context/engine.ts +31 −12` with instant diff expansion (e.g. key `d`).
695 +
696 +Build a beautiful terminal diff viewer (`/diff`): side-by-side when width permits; unified fallback; syntax highlighting; added/removed counts; file navigation; scrolling; accept/revert hooks in the future.
697 +
698 +### Streaming text UX
699 +
700 +Model text streams smoothly. Avoid terminal flicker, scroll jumps, full-screen re-renders, and cursor instability. The input area stays stable. Long tool output must not destroy the conversation layout.
701 +
702 +### Markdown rendering
703 +
704 +Render high-quality headings, bold, italic, inline code, code blocks, lists, tables, links, and quotes. Code blocks require syntax highlighting, horizontal scrolling or intelligent wrapping, and copy-friendly output. Never sacrifice terminal selection/copy behavior for visual tricks.
705 +
706 +### Thinking display
707 +
708 +When Anthropic surfaces reasoning through the API in a permitted way, keep its UI compact and consistent with API-permitted behavior. Do not build the product around exposing hidden reasoning. The interface primarily communicates: what the agent is doing · what tools it uses · what changed · what remains.
709 +
710 +### Interruption
711 +
712 +The user must be able to interrupt immediately (e.g. `Esc`). Cancellation propagates through model stream → tool execution → processes → agent loop, **without corrupting the session**.
713 +
714 +### Steering while working
715 +
716 +A major differentiator: the user can type while KHAELOR works. Steering messages are queued or safely injected at an appropriate boundary, displayed as `Queued instruction`. This requires careful concurrency design.
717 +
718 +### Shell mode
719 +
720 +Consider `!git status` in the composer to execute a shell command directly; results display in the conversation and may optionally become agent context.
721 +
722 +---
723 +
724 +## 15. TRANSPARENCY: COST AND CONTEXT
725 +
726 +### Cost visibility
727 +
728 +`/cost` shows session input/output tokens, cache reads/writes, and estimated cost — from **actual API usage metadata**. Never invent token usage.
729 +
730 +### Context inspector
731 +
732 +`/context` shows a budget breakdown (system, project instructions, conversation, repository context, tool observations, reserved output, total) and which files are materially represented in context. This makes KHAELOR understandable.
733 +
734 +---
735 +
736 +## 16. GIT AWARENESS
737 +
738 +KHAELOR must always understand: current branch · working tree changes · untracked files · existing user changes.
739 +
740 +- Never assume an existing diff belongs to KHAELOR.
741 +- Record baseline state before edits; clearly identify what KHAELOR changed afterward.
742 +- Do not auto-commit unless explicitly requested.
743 +- Protect user work at all times.
744 +
745 +---
746 +
747 +## 17. ERRORS, THE AGENT LOOP, AND COMPLETION
748 +
749 +### Error design
750 +
751 +Bad: `Error: command failed.`
752 +
753 +Good:
754 +
755 +```
756 +npm test failed
757 +2 tests failed
758 +src/context/engine.test.ts
759 + context compaction preserves running processes
760 +KHAELOR is inspecting the failure.
761 +```
762 +
763 +The agent normally consumes recoverable tool errors itself rather than making the user debug the agent.
764 +
765 +### Agent loop
766 +
767 +The agent iterates naturally: understand → inspect → plan internally → edit → run → observe → fix → verify → finish. Do not stop after writing code if verification is possible.
768 +
769 +### Completion standard
770 +
771 +KHAELOR never claims completion merely because the model generated a confident sentence. Before completing coding work, inspect applicable evidence: tests · type checking · linting · build · git diff · requirements · **file header compliance (Absolute Rule #0)**.
772 +
773 +Only run checks relevant to the repository — do not blindly launch massive unrelated test suites.
774 +
775 +Internally represent completion rigorously:
776 +
777 +```ts
778 +interface CompletionEvidence {
779 + objective: string;
780 + changedFiles: string[];
781 + checks: CheckResult[];
782 + unresolvedIssues: string[];
783 +}
784 +```
785 +
786 +The final user-facing response can remain concise.
787 +
788 +### Response style
789 +
790 +KHAELOR behaves like an elite technical collaborator. During work: short, specific, action-oriented. Avoid "I'll now…", "Next I'll…", "Great!", "Absolutely!". Prefer:
791 +
792 +```
793 +I found the state leak in SessionStore. Fixing that before touching the renderer.
794 +```
795 +
796 +At completion:
797 +
798 +```
799 +Implemented persistent session recovery.
800 +Changed
801 +- SessionStore now journals events atomically.
802 +- Startup restores interrupted sessions.
803 +- Added recovery tests.
804 +Checks
805 +- 148 tests passed
806 +- typecheck passed
807 +```
808 +
809 +---
810 +
811 +## 18. PERFORMANCE
812 +
813 +Performance is a feature. Measure: cold startup · input latency · render latency · tool dispatch · repository search · memory usage · session load. Do not optimize on intuition — create benchmarks where appropriate. The TUI must feel instantaneous even when the LLM requires time.
814 +
815 +**No spinner-driven UX.** The best latency UX is useful progress. Instead of `⠋ Thinking...`, prefer `● Reading src/session/store.ts` or `● Running tests · 41/148` when that information is actually known. Never fabricate progress.
816 +
817 +When latency appears, determine whether it comes from: model · network · filesystem · repository indexing · rendering · architecture. **Measure first. Optimize the correct layer.**
818 +
819 +---
820 +
821 +## 19. CONFIGURATION UX, THEMING, ACCESSIBILITY, PLATFORMS
822 +
823 +### Configuration UX
824 +
825 +`/config` opens a keyboard-navigable panel (model, agent behavior, interface, permissions). Configuration must also be editable as a file.
826 +
827 +### Theming
828 +
829 +Support terminal color capabilities intelligently. Ship **one exceptional default theme first**; `/theme` can come later. Respect `NO_COLOR`, terminal capabilities, and light/dark backgrounds where detectable. Do not prioritize theme customization over usability.
830 +
831 +### Accessibility
832 +
833 +Never communicate state through color alone — symbols and text must retain meaning in monochrome terminals. Ensure readable contrast. Keyboard-only use must be complete.
834 +
835 +### Cross-platform
836 +
837 +Primary targets: macOS and Linux. Design so Windows remains possible. Be careful about shell assumptions, path separators, PTY handling, signals, and terminal capabilities.
838 +
839 +### Logging
840 +
841 +Developer logs never pollute the TUI. Use `~/.khaelor/logs/`. Support `khaelor --debug`. Never log API keys, secret environment values, or authorization headers — redact sensitive values.
842 +
843 +---
844 +
845 +## 20. PROJECT STRUCTURE
846 +
847 +Derive the final language-specific structure after Phase 0. Conceptually:
848 +
849 +```
850 +src/
851 +├── cli/ app · commands · keyboard · lifecycle
852 +├── tui/ components/ · composer/ · markdown/ · diff/ · tool-view/ · palette/ · status/
853 +├── agent/ kernel · state · executor · completion
854 +├── anthropic/ client · streaming · messages · usage
855 +├── session/ session · events · store · checkpoint
856 +├── context/ engine · compaction · budget · project-context
857 +├── tools/ registry · read · write · edit · grep · glob · bash · process
858 +├── workspace/ local
859 +├── repository/ index · git · search
860 +├── permissions/ policy · evaluator
861 +├── config/ schema · loader · defaults
862 +└── shared/
863 +```
864 +
865 +Avoid circular dependencies. **Every file in `src/` carries the mandatory author header.**
866 +
867 +---
868 +
869 +## 21. TESTING
870 +
871 +Build tests while implementing.
872 +
873 +**Unit:** tool parsing · event reducer · context budgeting · config resolution · permission rules · file editing · process lifecycle · session persistence · **header lint check**.
874 +
875 +**Integration:** Anthropic streaming · tool execution loop · session resume · interruption · context compaction · repository modification.
876 +
877 +**TUI (where practical):** keyboard navigation · resize · long output · streaming · dialogs · permission requests · model selector · slash palette.
878 +
879 +**Golden/snapshot:** tool rendering · markdown · diffs · error panels · status lines. Do not make snapshots so broad that every intentional UI improvement becomes painful.
880 +
881 +---
882 +
883 +## 22. DOGFOODING AND BENCHMARKING
884 +
885 +### Dogfooding
886 +
887 +KHAELOR must be developed **using KHAELOR** as soon as it is sufficiently functional. Keep `docs/DOGFOOD_NOTES.md` recording friction, unexpected behavior, latency, UI annoyances, agent failures, context failures, and permission annoyances. Treat small UX friction as real bugs.
888 +
889 +### Benchmark UX against competitors
890 +
891 +After the first functional TUI exists, manually compare equivalent workflows against Claude Code, OpenCode, Hermes Agent, and OpenHands CLI. Do not copy visual appearance blindly.
892 +
893 +Measure workflows: start agent · select model · ask a repository question · inspect a tool call · approve a command · interrupt · resume · inspect diff · switch session · find file · run a background process · inspect context.
894 +
895 +Count: keystrokes · latency · screen noise · modal interruptions · clarity. KHAELOR should deliberately improve these workflows.
896 +
897 +---
898 +
899 +## 23. V1 SCOPE
900 +
901 +**V1 MUST include:**
902 +
903 +✓ exceptional TUI · ✓ Anthropic API · ✓ Anthropic model configuration · ✓ streaming · ✓ agent loop · ✓ persistent sessions · ✓ local workspace · ✓ read · ✓ write · ✓ edit · ✓ grep · ✓ glob · ✓ bash · ✓ background process manager · ✓ permission system · ✓ repository awareness · ✓ git awareness · ✓ context management · ✓ context compaction · ✓ slash commands · ✓ file mentions · ✓ command palette · ✓ model selector · ✓ diff viewer · ✓ usage/cost display · ✓ interruption · ✓ queued steering · ✓ tests · ✓ documentation · ✓ **mandatory file headers + enforcement check**
904 +
905 +**NOT V1 — do not let these derail the release:**
906 +
907 +OpenAI · Gemini · OpenRouter · local models · MCP · browser automation · computer vision · Docker · SSH · cloud execution · mobile app · web UI · multi-user server · full memory system · self-generated skills · complex multi-agent orchestration · marketplace · plugins.
908 +
909 +Design clean boundaries for them. Do not implement them yet.
910 +
911 +### Future architecture
912 +
913 +Future KHAELOR may gain: Subagents → Memory → Skills → automatic skill extraction → Browser → MCP → SSH/Docker → distributed execution. The V1 architecture must not make these impossible — but future flexibility is not an excuse for premature abstraction.
914 +
915 +---
916 +
917 +## 24. DEVELOPMENT PHASES
918 +
919 +**Phase 0 — Research.** Deliver `docs/research/*`. No major production implementation before this is complete.
920 +
921 +**Phase 1 — Architecture.** Deliver `ARCHITECTURE.md`, `EVENT_MODEL.md`, `TUI_DESIGN.md`, `TOOL_PROTOCOL.md`, `PERMISSION_MODEL.md`. Build small prototypes when necessary to validate choices.
922 +
923 +**Phase 2 — Terminal UI shell.** Startup, layout, composer, markdown, keyboard handling, stream rendering, status area, slash palette, command palette. The interface should already feel excellent using mocked model events.
924 +
925 +**Phase 3 — Anthropic integration.** Authentication, model config, streaming, tool calling, usage accounting, errors, retry, cancellation.
926 +
927 +**Phase 4 — Agent loop.** `AgentKernel`, `ToolRegistry`, `Executor`, `LocalWorkspace`, events.
928 +
929 +**Phase 5 — Core tools.** read, write, edit, grep, glob, bash, process.
930 +
931 +**Phase 6 — Sessions + context.** Persistent events, resume, context engine, compaction, checkpointing.
932 +
933 +**Phase 7 — Repository intelligence.** Git awareness, repository map, efficient search, file mentions, context retrieval.
934 +
935 +**Phase 8 — Polish.** Obsess over latency, keyboard workflows, tool rendering, diffs, permission UX, interruptions, long sessions, errors, resize behavior.
936 +
937 +---
938 +
939 +## 25. DISCIPLINE
940 +
941 +For every substantial component:
942 +
943 +1. Inspect the relevant reference implementation
944 +2. Understand its trade-offs
945 +3. Document the KHAELOR decision
946 +4. Implement a minimal clean version — **with the mandatory file header**
947 +5. Test
948 +6. Dogfood
949 +7. Simplify
950 +
951 +Do not create complexity merely because another agent framework has it.
952 +
953 +**Architecture rule.** Whenever tempted to add something to `AgentKernel`, ask: *can this be a service around the kernel?* Usually yes.
954 +
955 +**UX rule.** Whenever something technically works, ask: *is this the best terminal interaction we can design?* If no, the feature is not finished.
956 +
957 +**Speed rule.** When latency appears, measure first, then optimize the correct layer (model / network / filesystem / indexing / rendering / architecture).
958 +
959 +**Quality rule.** Never declare something complete merely because it compiles. For meaningful changes: inspect → test → use → verify.
960 +
961 +---
962 +
963 +## 26. FINAL PRODUCT STANDARD
964 +
965 +KHAELOR should eventually make someone who regularly uses Claude Code, OpenCode, Hermes, and OpenHands think:
966 +
967 +> *Why doesn't every terminal agent work like this?*
968 +
969 +The first version does not need every feature those systems possess. It needs something harder: **a fundamentally better core experience**. Build the smallest architecture capable of delivering that experience exceptionally well.
970 +
971 +---
972 +
973 +## 27. BEGIN
974 +
975 +Your first task is NOT to start coding KHAELOR. Your first task is:
976 +
977 +1. Inspect the current environment and repository
978 +2. Obtain or locate the reference repositories
979 +3. Deeply analyze Hermes Agent
980 +4. Deeply analyze OpenCode
981 +5. Deeply analyze OpenHands and its Software Agent SDK
982 +6. Inspect mini-SWE-agent for architectural minimalism
983 +7. Write the required research documents
984 +8. Derive KHAELOR's architectural decisions
985 +9. Produce `ARCHITECTURE.md` and `TUI_DESIGN.md`
986 +10. Only then begin implementation
987 +
988 +Do not stop at superficial README analysis. Trace real code paths. Read actual implementation files. Follow imports. Find the agent loops, the tool registries, the session state, the render loops, the permission checks, context construction, process execution, and persistence. Understand **why** each system works the way it does.
989 +
990 +Then build something better.
991 +
992 +---
993 +
994 +**Understand first. Design second. Implement third.**
995 +
996 +*Author: Simon-Pierre Boucher · contact@spboucher.ai*
added README.md +77 −0
@@ -0,0 +1,77 @@
1 +<!--
2 +KHAELOR
3 +File: README.md
4 +Author: Simon-Pierre Boucher
5 +Contact: contact@spboucher.ai
6 +-->
7 +
8 +# KHAELOR
9 +
10 +**A terminal-native autonomous engineering agent powered by Anthropic.**
11 +
12 +KHAELOR is not another Claude Code clone. It combines the strongest architectural ideas from Claude Code, Hermes Agent, OpenCode, OpenHands, and mini-swe-agent — with its own architecture and identity, built on one principle:
13 +
14 +```
15 +Understand first. Design second. Implement third.
16 +```
17 +
18 +## Install
19 +
20 +```sh
21 +npm install -g khaelor
22 +```
23 +
24 +Or via the hosted installer:
25 +
26 +```sh
27 +curl -fsSL https://www.khaelor.sh/install.sh | sh
28 +```
29 +
30 +## Usage
31 +
32 +```sh
33 +export ANTHROPIC_API_KEY=sk-ant-...
34 +khaelor # start the interactive TUI in the current project
35 +khaelor --model <model-id> # pick an Anthropic model
36 +khaelor --print "prompt" # non-interactive: run one agent turn, print the answer
37 +```
38 +
39 +Inside the TUI:
40 +
41 +- `/model` `/config` `/permissions` `/context` `/sessions` `/resume` `/new` `/compact` `/cost` `/status` `/diff` `/processes` `/help` `/quit`
42 +- `Ctrl+K` — universal command palette
43 +- `@` — fuzzy file mentions · `!cmd` — shell mode · `Esc` — interrupt immediately
44 +- Type while the agent works — steering messages are queued and injected safely
45 +
46 +## Architecture
47 +
48 +A deliberately small kernel surrounded by services:
49 +
50 +```
51 +TUI → Session Engine → Agent Kernel → { Context Engine · Tool Runtime · Model Runtime }
52 + Repository Index · Workspace · Permissions
53 +```
54 +
55 +- **Event-sourced sessions** — append-only JSONL logs; resume = replay
56 +- **First-class streaming** — typed event bus, 16 ms coalesced rendering
57 +- **7 powerful tools** — read · write · edit (9-strategy replacer) · grep · glob · bash · process (background process manager)
58 +- **Capability-based permissions** — allow/ask/deny with precise, persisted grants
59 +- **Context engine** — token-pressure-aware compaction with structured checkpoints, prompt-cache byte-stability
60 +- **Custom ANSI renderer** — print-once scrollback, bounded live region, flat memory, sub-millisecond input latency
61 +
62 +Design documents live in `docs/` (`ARCHITECTURE.md`, `EVENT_MODEL.md`, `TUI_DESIGN.md`, `TOOL_PROTOCOL.md`, `PERMISSION_MODEL.md`); the Phase 0 reference research lives in `docs/research/`.
63 +
64 +## Development
65 +
66 +```sh
67 +npm install
68 +npm run check # typecheck + lint + tests + mandatory header check
69 +npm run dev # run from source
70 +npx tsx src/tui/demo.ts # TUI demo with mocked model events
71 +```
72 +
73 +V1 is Anthropic-only by design. The model layer is isolated behind a single `ModelClient` interface — clean architecture, not a provider framework.
74 +
75 +## Author
76 +
77 +Simon-Pierre Boucher — contact@spboucher.ai
added docs/ARCHITECTURE.md +776 −0
@@ -0,0 +1,776 @@
1 +<!--
2 +KHAELOR
3 +File: docs/ARCHITECTURE.md
4 +Author: Simon-Pierre Boucher
5 +Contact: contact@spboucher.ai
6 +-->
7 +
8 +# KHAELOR V1 — System Architecture
9 +
10 +> **Status:** Phase 1 deliverable — the definitive system design for KHAELOR V1.
11 +> **Inputs:** `CLAUDE.md` (product spec), `docs/research/KHAELOR_ARCHITECTURE_DECISIONS.md` (ADR-1…17, binding), `docs/research/COMPARATIVE_ARCHITECTURE.md` (evidence).
12 +> **Companion:** `docs/EVENT_MODEL.md` (the complete typed event vocabulary — normative for every event named here).
13 +>
14 +> Phase 2 implementation starts from this document. Where this document and an ADR disagree, the ADR wins and this document must be corrected.
15 +
16 +---
17 +
18 +## 1. Overview
19 +
20 +KHAELOR V1 is a single-process, terminal-native, Anthropic-only agent (ADR-1, ADR-17). Its architecture rests on five load-bearing decisions:
21 +
22 +1. **A tiny, state-derived kernel** (ADR-2). The loop re-derives "what next" from recorded session state each iteration. The kernel coordinates; services own everything else.
23 +2. **An event-sourced session** (ADR-3, ADR-4). One append-only JSONL file per session is the sole source of truth. UI state, LLM message history, cost, and metadata are all projections. Resume = replay.
24 +3. **First-class streaming** (ADR-5). Everything is an event; deltas flow ephemerally to the TUI, completed blocks flow durably to the log. There is no non-streaming path.
25 +4. **Prompt-cache byte-stability as an invariant** (ADR-7). History is never rewritten; compaction — recorded as an event — is the sole sanctioned break.
26 +5. **One world seam** (ADR-13). Tools act on the world only through `Workspace`. No `node:fs` / `node:child_process` outside `src/workspace/` (and the narrow, listed exemptions).
27 +
28 +### 1.1 System diagram (refined from CLAUDE.md §4)
29 +
30 +```
31 + ┌─────────────────────────────┐
32 + │ KHAELOR TUI │
33 + │ composer · timeline · bars │
34 + └────┬───────────────▲────────┘
35 + KernelCommand│ │ coalesced events (~16 ms)
36 + │ │
37 + ┌─────────────────────────────▼───────────────┴─────────────────────────────┐
38 + │ EVENT BUS (typed, in-process) │
39 + │ durable events ──► SessionStore (JSONL append, write-ahead) │
40 + │ ephemeral deltas ──► TUI coalescer only │
41 + └────────────▲───────────────────▲──────────────────────▲───────────────────┘
42 + │ │ │
43 + ┌───────┴────────┐ ┌───────┴────────┐ ┌─────────┴─────────┐
44 + │ Session Engine │ │ Agent Kernel │ │ Process Manager │
45 + │ log·projections│◄─┤ (state-derived│ │ (background procs)│
46 + │ resume=replay │ │ loop, ADR-2) │ └─────────▲─────────┘
47 + └───────▲────────┘ └───┬───┬───┬────┘ │
48 + │ │ │ │ │
49 + │ ┌────────┘ │ └─────────┐ │
50 + │ ▼ ▼ ▼ │
51 + │ ┌──────────┐ ┌──────────┐ ┌───────────┐ │
52 + │ │ Context │ │ Model │ │ Tool │──┘
53 + │ │ Engine │ │ Runtime │ │ Runtime │──► Permissions
54 + │ │ (ADR-6/7)│ │ (ADR-10) │ │ (ADR-8) │ (ADR-9)
55 + │ └────┬─────┘ └────┬─────┘ └─────┬─────┘
56 + │ │ │ │
57 + └──────┤ Anthropic API ▼
58 + ▼ (official SDK) ┌───────────┐
59 + ┌─────────────┐ │ Workspace │──► files · processes
60 + │ Repository │───────────────►│ (ADR-13) │
61 + │ Intelligence│ └───────────┘
62 + └─────────────┘
63 +```
64 +
65 +Reading the diagram:
66 +
67 +- The **TUI never calls the engine directly.** It emits `KernelCommand`s (submit message, interrupt, approve permission, run slash command) and consumes events. Nothing imports the TUI (see §2).
68 +- The **event bus is the only "API"** (ADR-17). Durable events are appended to the session log *before* being published (write-ahead, see `EVENT_MODEL.md §5`).
69 +- The **kernel** touches exactly five services: Context Engine, Model Runtime, Tool Runtime, Session Engine (via its projection handle), and the verification gate. Permissions, workspace, and repository intelligence sit behind those services.
70 +
71 +---
72 +
73 +## 2. Module Map and Dependency Rules
74 +
75 +### 2.1 Dependency direction rules (normative)
76 +
77 +Modules are arranged in strict layers. **An arrow means "may import"; anything not listed is forbidden.** ESLint (`import/no-restricted-paths` or `eslint-plugin-boundaries`) enforces this in CI alongside the header check.
78 +
79 +```
80 +Layer 0 shared (imports nothing)
81 +Layer 1 config · workspace · anthropic · session
82 +Layer 2 permissions · repository · tools · context
83 +Layer 3 agent
84 +Layer 4 tui
85 +Layer 5 cli (composition root)
86 +```
87 +
88 +Rules, explicitly:
89 +
90 +1. **`shared` imports nothing** (except `node:` builtins for pure utilities). The event vocabulary, envelope types, the `EventBus` interface, `KernelCommand`, `Result`, and error base types live here — this is what keeps the graph acyclic, since every layer speaks in these types.
91 +2. **Nothing imports `tui` except `cli`.** The TUI is a pure consumer of events and producer of `KernelCommand`s.
92 +3. **`tui` may import only `shared` and `config`** (theme/keybinding types). It renders from its own view-model, built by folding events (see `EVENT_MODEL.md §6.1`). It never imports `agent`, `session`, `tools`, or `anthropic`.
93 +4. **`tools` may import `workspace`, `repository`, and `shared` only.** Tools never check permissions themselves (the Tool Runtime gates them, §5.4) and never touch Node fs/process globals.
94 +5. **`workspace` is the only module that imports `node:fs` and `node:child_process`.** Exemptions (each individually listed in the lint rule config): `session/store` (its own log file I/O), `config/loader` (config file I/O), `cli` (bootstrap), `shared/logging` (log files under `~/.khaelor/logs/`).
95 +6. **`anthropic` imports `shared` only** (plus the `@anthropic-ai/sdk` package). Config values are passed in; it never reads config or sessions.
96 +7. **`session` imports `shared` only.** It owns the JSONL store and all projections.
97 +8. **`context` may import `session` (projection types), `repository`, `anthropic` (the `ModelClient` interface, for the auxiliary compaction model), and `shared`.**
98 +9. **`agent` may import `anthropic`, `tools`, `context`, `session`, `permissions`, `workspace`, and `shared`.** It is the only module that wires services together below `cli`.
99 +10. **`cli` may import everything.** It is the composition root: it constructs the bus, store, workspace, runtimes, kernel, and TUI, and wires `KernelCommand` dispatch.
100 +11. **No module imports another module's internals** — only its `index.ts` barrel. Deep imports fail lint.
101 +12. **No cycles, ever.** A cycle is a build failure, not a warning.
102 +
103 +### 2.2 Module responsibilities and public interfaces
104 +
105 +Interface sketches below are the *public barrel* of each module — real, compilable-looking signatures that Phase 2–5 implement. Event types referenced (e.g. `DurableEvent`) are defined normatively in `EVENT_MODEL.md`.
106 +
107 +#### `src/shared/` — Layer 0
108 +
109 +Event vocabulary and envelopes, event bus, kernel commands, shared result/error types, logging, ids (ULID), token/byte utilities. **No business logic.**
110 +
111 +```ts
112 +// shared/events.ts — full definitions in EVENT_MODEL.md
113 +export type DurableEvent = /* discriminated union, EVENT_MODEL.md §4 */;
114 +export type EphemeralEvent = /* discriminated union, EVENT_MODEL.md §4 */;
115 +export type KhaelorEvent = DurableEvent | EphemeralEvent;
116 +
117 +export interface EventBus {
118 + publishDurable(e: DurableEventInput): DurableEvent; // append-then-publish; assigns seq/id/ts
119 + publishEphemeral(e: EphemeralEvent): void;
120 + on<T extends KhaelorEvent["type"]>(
121 + type: T,
122 + handler: (e: Extract<KhaelorEvent, { type: T }>) => void,
123 + ): Unsubscribe;
124 + onAny(handler: (e: KhaelorEvent) => void): Unsubscribe;
125 +}
126 +
127 +// shared/commands.ts — the TUI→engine channel
128 +export type KernelCommand =
129 + | { kind: "submit-message"; text: string; mentions: FileMention[] }
130 + | { kind: "steer"; text: string }
131 + | { kind: "interrupt" }
132 + | { kind: "permission-response"; requestId: string; decision: "once" | "always" | "deny" }
133 + | { kind: "slash"; command: string; args: string }
134 + | { kind: "shell"; command: string } // "!git status" composer shortcut
135 + | { kind: "quit" };
136 +
137 +export type Unsubscribe = () => void;
138 +export type Result<T, E = KhaelorError> = { ok: true; value: T } | { ok: false; error: E };
139 +```
140 +
141 +#### `src/config/` — Layer 1
142 +
143 +Schema, loading, precedence merge (CLI flags → `.khaelor/config.json` → `~/.khaelor/config.json` → env → defaults), secret hygiene (never log/echo keys), `KHAELOR.md` / `CLAUDE.md` / `AGENTS.md` instruction-file discovery with precedence.
144 +
145 +```ts
146 +export interface KhaelorConfig {
147 + model: string; // Anthropic model id — never hard-coded lists (CLAUDE.md §6)
148 + auxModel: string; // cheaper model for compaction summaries (ADR-10)
149 + thinking: "off" | "adaptive" | "always";
150 + maxOutputTokens: number;
151 + permissions: Record<string, PermissionAction>; // capability → allow|ask|deny
152 + theme: ThemeConfig;
153 +}
154 +export function loadConfig(argv: ParsedArgs, cwd: string): Promise<ResolvedConfig>;
155 +export function discoverInstructions(cwd: string): Promise<InstructionFile[]>; // ordered, global → nested
156 +export function persistPermissionGrant(scope: "project", rule: PermissionRule): Promise<void>; // ADR-9
157 +```
158 +
159 +#### `src/workspace/` — Layer 1
160 +
161 +The world seam (ADR-13). Exactly four methods plus process spawning support used by the Process Manager. `LocalWorkspace` is the only V1 implementation.
162 +
163 +```ts
164 +export interface Workspace {
165 + cwd(): string;
166 + readFile(path: string): Promise<string>;
167 + writeFile(path: string, content: string): Promise<void>; // atomic: tmp + rename
168 + exec(command: Command): Promise<ProcessResult>;
169 +}
170 +export interface Command {
171 + cmd: string; // run via user shell for `bash` tool
172 + cwd?: string;
173 + timeoutMs: number; // hard ceiling; ADR-8: long commands redirect to `process`
174 + env?: Record<string, string>;
175 + signal?: AbortSignal;
176 +}
177 +export interface ProcessResult {
178 + exitCode: number | null; // null = killed by timeout/signal
179 + stdout: string; stderr: string;
180 + durationMs: number;
181 + truncated: boolean;
182 +}
183 +export class LocalWorkspace implements Workspace { /* only module touching node:fs/child_process */ }
184 +// Tool-side helpers PARAMETERIZED BY Workspace (never fattening the interface — ADR-13):
185 +export function statFile(ws: Workspace, path: string): Promise<FileStat>;
186 +export function fileExists(ws: Workspace, path: string): Promise<boolean>;
187 +```
188 +
189 +#### `src/anthropic/` — Layer 1
190 +
191 +`ModelClient` over the official SDK (ADR-10). Streaming, typed errors, retry/backoff, cancellation, honest usage. See §7.
192 +
193 +#### `src/session/` — Layer 1
194 +
195 +Event log store (JSONL), projections, resume-by-replay, session metadata, checkpoint types. See §6.
196 +
197 +#### `src/permissions/` — Layer 2
198 +
199 +Capability policy and evaluator (ADR-9).
200 +
201 +```ts
202 +export type Capability =
203 + | "file.read" | "file.write.project" | "process.execute"
204 + | "network.access" | "git.modify" | "filesystem.outsideProject";
205 +export type PermissionAction = "allow" | "ask" | "deny";
206 +export interface PermissionRule { capability: Capability; pattern: string; action: PermissionAction; }
207 +
208 +export interface PermissionRequest {
209 + capability: Capability;
210 + descriptor: string; // e.g. "bash: npm install", "write: src/agent/kernel.ts"
211 + toolUseId: string;
212 + suggestion?: PermissionRule; // "always allow `git push *`" — omitted when the command
213 +} // contains shell operators (Hermes guard, ADR-9)
214 +
215 +export interface PermissionEvaluator {
216 + evaluate(req: PermissionRequest): PermissionAction; // last-match-wins over rules + hardline floor
217 + addGrant(rule: PermissionRule): Promise<void>; // persists via config (ADR-9)
218 +}
219 +```
220 +
221 +The evaluator is deterministic: a small unbypassable deny floor (checked against de-obfuscated command variants) → configured rules, last match wins with wildcards → default `ask` for unmatched destructive capabilities. Denials produce feedback text delivered to the model as an observation (ADR-9, rejection-with-feedback).
222 +
223 +#### `src/repository/` — Layer 2
224 +
225 +Repository intelligence, exposed **only** through the Context Engine (CLAUDE.md §11) and the tools that wrap it. V1: filesystem map, git status/diff/baseline, ripgrep search, frecency-ranked file finding for `@` mentions. No AST/embeddings/symbol index.
226 +
227 +```ts
228 +export interface RepositoryIndex {
229 + fileMap(opts?: { maxEntries?: number }): Promise<RepoFileEntry[]>; // respects .gitignore/.khaelorignore
230 + gitStatus(): Promise<GitStatus>; // branch, dirty, untracked
231 + gitDiff(paths?: string[]): Promise<GitDiff>;
232 + recordBaseline(when: "session-start" | "pre-first-edit"): Promise<GitBaseline>; // ADR-15
233 + attributeChanges(): Promise<{ khaelor: string[]; preExisting: string[] }>;
234 + search(query: RipgrepQuery): Promise<SearchResult>; // bounded, structured
235 + findFiles(fuzzy: string, limit: number): Promise<RankedFile[]>; // frecency-ranked
236 + noteAccess(path: string): void; // feeds frecency + recency
237 +}
238 +```
239 +
240 +#### `src/tools/` — Layer 2
241 +
242 +The seven primitives (ADR-8): `read · write · edit · grep · glob · bash · process`, plus the registry and the process manager. Each tool: ≤5 parameters, rich description, structured result with model-facing text and UI-facing metadata.
243 +
244 +```ts
245 +export interface ToolDefinition<In> {
246 + name: ToolName;
247 + description: string; // guidance prose lives here, not in params
248 + inputSchema: JSONSchema; // ≤5 properties
249 + capability: (input: In, ctx: ToolContext) => Capability; // drives permission evaluation
250 + execute: (input: In, ctx: ToolContext) => Promise<ToolResult>;
251 +}
252 +export interface ToolContext {
253 + workspace: Workspace;
254 + repo: RepositoryIndex;
255 + processes: ProcessManager;
256 + signal: AbortSignal; // turn-scoped; aborts in-flight tools (ADR-11)
257 + emitOutput: (chunk: string) => void; // → ephemeral ToolOutput events
258 + budget: OutputBudget; // per-result + per-turn caps, spill-to-file (ADR-8)
259 +}
260 +export interface ToolResult {
261 + modelText: string; // budgeted head/tail-truncated text with omission markers
262 + isError: boolean;
263 + spillFile?: string; // full output path under ~/.khaelor/spill/, model can read/grep it
264 + ui: ToolUiMeta; // diffStats, matchCount, exitCode… for collapsed rendering
265 + filesModified?: FileModification[];
266 +}
267 +export interface ToolRegistry {
268 + definitions(): ToolDefinition<unknown>[]; // data-driven — the ADR-16 seam
269 + get(name: string): ToolDefinition<unknown> | undefined;
270 +}
271 +export interface ProcessManager { // model-facing via the `process` tool (ADR-8)
272 + start(cmd: string, opts: { cwd?: string; name?: string }): ManagedProcess;
273 + list(): ManagedProcessInfo[];
274 + read(id: string, opts?: { offset?: number; limit?: number }): ProcessReadResult; // rolling 200K buffer
275 + write(id: string, input: string): void;
276 + stop(id: string): Promise<void>; // process-group kill (mini hygiene, ADR-8)
277 + stopAll(): Promise<void>; // shutdown only — NOT on interrupt (ADR-11)
278 +}
279 +```
280 +
281 +`edit` implements the replacer cascade (exact → line-trimmed → block-anchor → whitespace-normalized → indentation-flexible → escape-normalized → trimmed-boundary → context-aware → multi-occurrence) with uniqueness and disproportionate-match guards, CRLF/BOM preservation, atomic writes, and OpenHands-grade failure messages (line-number hints, "maybe you meant", post-edit snippet). Every successful `write`/`edit` emits a durable `FileModified` event carrying diff stats and a capped unified diff.
282 +
283 +#### `src/context/` — Layer 2
284 +
285 +The Context Engine (ADR-6/7). See §8.
286 +
287 +#### `src/agent/` — Layer 3
288 +
289 +`AgentKernel` (the loop), `deriveNext` (pure state → decision), the stream-event reducer, the Tool Runtime (permission gate + execution + observation recording), the verification gate (ADR-12), steering queue, interruption controller. See §5.
290 +
291 +#### `src/tui/` — Layer 4
292 +
293 +Terminal UI: app shell, timeline, composer, markdown renderer (settled-block streaming), diff viewer, tool views, palettes, status bar, permission panel, coalescer. Framework selected by the Phase 1 spike (ADR-14); this module's *external contract* is framework-independent:
294 +
295 +```ts
296 +export interface TuiApp {
297 + start(io: { events: EventBus; dispatch: (c: KernelCommand) => void; config: ResolvedConfig }): Promise<void>;
298 + stop(): Promise<void>;
299 +}
300 +```
301 +
302 +Internally, a `Coalescer` batches ephemeral deltas at ~16 ms (contract in `EVENT_MODEL.md §7`); the view-model is a fold over events; rendering uses settled-block incremental markdown with a bounded live region (hard caps on live-region chars/lines). No 100-message scrollback cliff.
303 +
304 +#### `src/cli/` — Layer 5
305 +
306 +Entry point, argv parsing, config resolution, composition root, lifecycle (signals, terminal setup/teardown, crash-safe restore), lazy-import discipline (§13), `khaelor --debug` logging switch.
307 +
308 +---
309 +
310 +## 3. Data & Control Flow (one turn, end to end)
311 +
312 +```
313 +user types → composer → KernelCommand{submit-message} → cli dispatcher
314 + → durable UserMessageCreated appended + published
315 + → kernel loop wakes (it is the sole consumer of "work exists" state)
316 + → ContextEngine.selectContext(projection) → ModelRequest (byte-stable tiers, ADR-7)
317 + → ModelClient.stream(request, signal)
318 + deltas → ephemeral ModelTextDelta / ModelThinkingDelta / ToolInputDelta → TUI coalescer
319 + settled → durable ModelTextBlockCompleted / ModelThinkingBlockCompleted / ToolRequested
320 + finish → durable ModelResponseCompleted {usage, stopReason}
321 + → for each ToolRequested (sequential, block order):
322 + Tool Runtime: capability → PermissionEvaluator
323 + ask → durable PermissionRequested → TUI panel → PermissionGranted/Denied
324 + durable ToolApproved → ToolStarted → execute (ephemeral ToolOutput chunks)
325 + → durable ToolCompleted | ToolFailed | ToolCancelled (+ FileModified / ProcessStarted …)
326 + → steering queue drained at the post-tool-batch seam → durable SteeringInjected
327 + → ContextEngine.onTurnComplete(usage) → maybe durable ContextPruned / ContextCompacted
328 + → loop re-derives: more tool calls? overflow? stop?
329 + → on stop with unverified code changes → verification gate (≤2 nudges) → TaskCompleted{evidence}
330 +```
331 +
332 +Every durable event is appended to the JSONL **before** subscribers see it. The TUI, the session store, and the LLM history projection all consume the same stream — streaming, history, and state cannot diverge.
333 +
334 +---
335 +
336 +## 4. The Agent Kernel (ADR-2)
337 +
338 +The kernel is a small loop that re-derives "what next" from recorded session state each iteration. Exit conditions derive from state, never from in-memory flags. Target size: a few hundred lines including the reducer — anything larger is carrying non-kernel work.
339 +
340 +### 4.1 Kernel state
341 +
342 +Per ADR-2 / mini's rule ("if a field isn't consulted by the loop itself, it belongs to a service"), the kernel holds exactly:
343 +
344 +```ts
345 +interface KernelDeps {
346 + session: SessionHandle; // projection access + durable publish
347 + context: ContextEngine;
348 + model: ModelClient;
349 + tools: ToolRuntime; // registry + permission gate + executor
350 + verifier: VerificationGate; // ADR-12
351 + steering: SteeringQueue; // ADR-11
352 + bus: EventBus;
353 +}
354 +interface KernelRunState {
355 + turnSignal: AbortController; // the cancellation root for this run (ADR-11)
356 + budget: { iterations: number; verificationAttempts: number };
357 +}
358 +```
359 +
360 +### 4.2 The loop (precise pseudocode)
361 +
362 +```ts
363 +async function runTurn(deps: KernelDeps, run: KernelRunState): Promise<TurnOutcome> {
364 + while (true) {
365 + // 1. Re-derive what to do from RECORDED state — never from loop-local flags.
366 + const state = deps.session.projection(); // in-memory, event-derived (rebuilt from
367 + const next = deriveNext(state, run.budget); // JSONL only on resume — ADR-2 trade-off)
368 +
369 + switch (next.kind) {
370 + case "inject-steering": {
371 + // Safe seam: after tool results / before the next model call (ADR-11).
372 + deps.session.publish(steeringInjected(next.queued, next.seam));
373 + continue;
374 + }
375 +
376 + case "compact": {
377 + // Proactive (token budget) or reactive (overflow error recorded). ADR-6.
378 + const checkpoint = await deps.context.compress(state, run.turnSignal.signal);
379 + deps.session.publish(contextCompacted(checkpoint)); // durable; replay-deterministic
380 + continue;
381 + }
382 +
383 + case "call-model": {
384 + const ctx = await deps.context.selectContext(state); // byte-stable tiers (ADR-7)
385 + try {
386 + for await (const ev of deps.model.stream(ctx.request, run.turnSignal.signal)) {
387 + reduceModelEvent(ev, deps); // pure reducer: deltas → ephemeral publish;
388 + } // settled blocks / tool_use / usage → durable publish
389 + } catch (err) {
390 + const klass = classifyModelError(err); // typed taxonomy (ADR-10)
391 + deps.session.publish(modelRequestFailed(klass));
392 + if (klass.kind === "context-overflow") continue; // → deriveNext yields "compact"
393 + if (klass.kind === "cancelled") continue; // → deriveNext sees Interrupted
394 + if (klass.retryable) continue; // ModelClient already backed off
395 + return { kind: "failed", error: klass }; // fatal → TaskFailed recorded by caller
396 + }
397 + continue;
398 + }
399 +
400 + case "execute-tools": {
401 + // Sequential, block order. The runtime gates permissions, executes, and records
402 + // ToolApproved/Started/Completed/Failed/Cancelled + FileModified/Process* itself.
403 + await deps.tools.executeBatch(next.pending, run.turnSignal.signal);
404 + deps.context.onTurnComplete(deps.session.projection().lastUsage); // ADR-6 observation
405 + continue;
406 + }
407 +
408 + case "verify": {
409 + // Model stopped; code changed this turn; no fresh verification evidence (ADR-12).
410 + if (run.budget.verificationAttempts >= 2) return { kind: "done", withheld: next.candidate };
411 + run.budget.verificationAttempts++;
412 + deps.session.publish(verificationRequested(next.detectedChecks, next.candidate));
413 + continue; // nudge is a synthetic message → call-model
414 + }
415 +
416 + case "interrupted": return { kind: "interrupted" }; // Interrupted event is already durable
417 + case "done": return { kind: "done" };
418 + }
419 + }
420 +}
421 +```
422 +
423 +### 4.3 `deriveNext` — a pure function of recorded state
424 +
425 +```ts
426 +function deriveNext(s: SessionProjection, budget: Budget): NextAction {
427 + if (s.interruptedSinceLastModelCall) return { kind: "interrupted" };
428 + if (s.pendingToolCalls.length > 0) return { kind: "execute-tools", pending: s.pendingToolCalls };
429 + if (s.queuedSteering.length > 0 && s.atSafeSeam) return { kind: "inject-steering", ... };
430 + if (s.contextOverflowRecorded || s.tokensUsed > s.compactionThreshold)
431 + return { kind: "compact" };
432 + if (s.lastStop === "end_turn") {
433 + const gate = needsVerification(s); // code changed this turn ∧ no fresh evidence,
434 + if (gate.required) // documentation-only changes filtered (ADR-12)
435 + return { kind: "verify", ...gate };
436 + return { kind: "done" };
437 + }
438 + if (isDoomLoop(s)) return { kind: "verify-with-user" }; // 3 byte-identical
439 + if (budget.iterations <= 0) return { kind: "done" }; // consecutive calls → ask
440 + return { kind: "call-model" };
441 +}
442 +```
443 +
444 +Properties this buys (OpenCode evidence, ADR-2): a crashed process resumes mid-conversation because nothing lives only in loop-local variables; steering and interruption are just state the next iteration observes; compaction is a task the loop derives, not a side effect buried in a handler. The stream reducer (`reduceModelEvent`) is a separate pure component from the loop.
445 +
446 +### 4.4 What is *not* in the kernel
447 +
448 +Retry/backoff (Model Runtime), permission evaluation (Tool Runtime → Permissions), output budgeting (tools), compaction algorithm (Context Engine), rendering (TUI), persistence mechanics (Session Store), git baselines (Repository). The doom-loop check and the verification gate are the only kernel-adjacent policies, and both are pure functions over the projection.
449 +
450 +---
451 +
452 +## 5. Session Engine (ADR-3)
453 +
454 +### 5.1 Storage layout
455 +
456 +```
457 +~/.khaelor/
458 + sessions/<project-hash>/
459 + <session-id>.jsonl # append-only durable event log — THE truth
460 + <session-id>.meta.json # projection cache (title, usage, cost, updatedAt) — always rebuildable
461 + spill/ # oversized tool output (size-capped)
462 + logs/ # developer logs (never the TUI)
463 +```
464 +
465 +`project-hash` = hash of the git root (or cwd when not a repo). Session ids are ULIDs — lexicographic order = creation order.
466 +
467 +### 5.2 The store
468 +
469 +```ts
470 +export interface SessionStore {
471 + create(meta: NewSessionMeta): Promise<SessionHandle>;
472 + open(sessionId: string): Promise<SessionHandle>; // resume = replay (§5.4)
473 + list(projectHash: string): Promise<SessionMeta[]>; // reads meta caches; rebuilds stale ones
474 +}
475 +export interface SessionHandle {
476 + readonly id: string;
477 + publish(e: DurableEventInput): DurableEvent; // append (write-ahead) + bus publish; assigns seq
478 + projection(): SessionProjection; // in-memory, maintained by the same reducer
479 + meta(): SessionMeta;
480 +}
481 +export interface SessionMeta { // CLAUDE.md §8
482 + id: string; title: string; project: string;
483 + createdAt: number; updatedAt: number;
484 + model: string;
485 + tokenUsage: UsageTotals; // incl. cache read/write — real API numbers only
486 + cost: number;
487 + gitBranch: string;
488 + workingDirectory: string;
489 +}
490 +```
491 +
492 +Durability discipline (mini's `finally` rule, ADR-3): the log is appended at every loop boundary — after every durable event, before the bus publishes it. Append atomicity, fsync policy, corruption recovery (truncated last line), and the versioning strategy are specified normatively in `EVENT_MODEL.md §5`.
493 +
494 +### 5.3 Projections
495 +
496 +All state is derived. Five projections, each a fold over the durable stream (full rules in `EVENT_MODEL.md §6`):
497 +
498 +| Projection | Consumers | Notes |
499 +|---|---|---|
500 +| `SessionProjection` | kernel (`deriveNext`) | pending tool calls, queued steering, last stop reason, overflow flag, files changed this turn, verification evidence |
501 +| `LlmHistory` | Context Engine | byte-stable Anthropic `messages[]`; re-applies `ContextCompacted`/`ContextPruned` deterministically |
502 +| `Timeline` | TUI view-model | messages, tool cards, diffs, status |
503 +| `UsageTotals` | `/cost`, status bar | summed from `ModelResponseCompleted.usage` only — never invented (Absolute Rule #4) |
504 +| `FileChangeSet` | `/diff`, verification gate, attribution | vs. `BaselineRecorded` (ADR-15) |
505 +
506 +Projections are caches, never truth (ADR-3 concern): each must tolerate being stale or deleted and rebuild from the log.
507 +
508 +### 5.4 Resume
509 +
510 +`open()` replays the JSONL through the same reducer that maintains live projections. Resume contract (OpenHands-derived, ADR-3): tools add-only, model swappable. On replay, any `ToolRequested` without a terminal result gets a synthetic durable `ToolCancelled` appended **at resume time** so the LLM history is protocol-valid (`EVENT_MODEL.md §6.5`). Background processes do not survive the process; `ProcessExited{cause:"khaelor-shutdown"}` is recorded at shutdown, and resume renders them as exited.
511 +
512 +Long-session replay cost is bounded by `ContextCompacted` events, which act as natural snapshots for `LlmHistory`; if `Timeline` replay is ever measured slow, periodic snapshot events are the sanctioned fix — not a database.
513 +
514 +### 5.5 Session commands
515 +
516 +`/sessions` (list via meta caches) · `/resume` · `/new` · `/rename` (durable `SessionRenamed`) · `/clear` (new session; never truncates a log). `/branch` and `/rewind` are post-V1; the envelope reserves `parentId?` (ADR-3).
517 +
518 +---
519 +
520 +## 6. Context Engine (ADR-6, ADR-7)
521 +
522 +### 6.1 Interface
523 +
524 +Hermes' verbs, exactly four:
525 +
526 +```ts
527 +export interface ContextEngine {
528 + /** Assemble the model request from the projection. V1: pass-through hook for selection
529 + * (no retrieval/topic routing yet — the verb exists so repository intelligence can
530 + * plug in without interface change). */
531 + selectContext(s: SessionProjection): Promise<BuiltContext>;
532 +
533 + /** Summarize-compact. Emits nothing itself — returns the checkpoint the kernel records
534 + * as a durable ContextCompacted event. Cuts ONLY at pairing-safe indices. */
535 + compress(s: SessionProjection, signal: AbortSignal): Promise<CompactionCheckpoint>;
536 +
537 + /** Observation hook: real usage from the last response updates budget accounting. */
538 + onTurnComplete(usage: ModelUsage): void;
539 +
540 + /** Cheap, deterministic, no-LLM: blank old tool results (protect newest N tokens).
541 + * Returns the toolUseIds to prune; kernel records ContextPruned. Runs BEFORE compress. */
542 + pruneToolResults(s: SessionProjection): PruneDecision;
543 +}
544 +export interface BuiltContext {
545 + request: ModelRequest; // system tiers + messages + tools + cache breakpoints
546 + stats: ContextStats; // per-section token estimates → /context inspector
547 +}
548 +```
549 +
550 +### 6.2 Triggers — token-based, from real usage only
551 +
552 +- **Proactive:** after each response, `onTurnComplete` compares `usage.inputTokens + usage.outputTokens` against `usableWindow = modelWindow − reservedOutput − compactionBuffer`. Crossing the threshold sets the projection's compaction flag; `deriveNext` yields `compact` at the next safe boundary. Never event-count triggers (OpenHands' named weakness).
553 +- **Reactive:** a `context-overflow` typed model error (ADR-10) records `ModelRequestFailed{kind:"context-overflow"}`; `deriveNext` routes to `compact` — never a blind retry.
554 +- **Order:** `pruneToolResults` first (cheap, deterministic — protect the newest ~40K tokens of tool output); `compress` only if still over budget.
555 +
556 +### 6.3 Compaction-as-event
557 +
558 +`ContextCompacted` is a durable event carrying the checkpoint and the exact cut range. `LlmHistory` re-applies it deterministically on every rebuild — replay-safe, inspectable (`/context`, `/compact`), and itself re-compactable later. Cut indices are chosen only where every `tool_use` before the cut has its `tool_result` before the cut (pairing safety — `EVENT_MODEL.md §6.5`). Compaction summaries route to the configured `auxModel` (ADR-10).
559 +
560 +### 6.4 Checkpoint format (structured YAML — CLAUDE.md §12)
561 +
562 +```yaml
563 +objective: <the user's current objective, one paragraph>
564 +completed:
565 + - <finished sub-goal>
566 +current_state: <where the work stands right now>
567 +important_files:
568 + - path: src/context/engine.ts
569 + reason: <why it matters to remaining work>
570 +changes:
571 + - <file-level change made so far>
572 +failed_attempts:
573 + - <approach tried and abandoned, with why>
574 +decisions:
575 + - <decision taken and rationale>
576 +running_processes:
577 + - id: <process id>
578 + command: npm run dev
579 + status: running
580 +next_steps:
581 + - <concrete next action>
582 +raw_evidence: # preserved verbatim when summarization would destroy it
583 + - label: <e.g. failing test output>
584 + content: |
585 + <capped raw text>
586 +```
587 +
588 +`failed_attempts`, `decisions`, and `running_processes` exist precisely because free-text summaries destroy them (ADR-6 trade-off).
589 +
590 +### 6.5 Prompt-cache byte-stability rules (ADR-7 — enforced by tests)
591 +
592 +1. **The system prompt is built once per session** in stable tiers: `[identity/behavior] → [tool guidance] → [project instructions]`. It is never re-rendered mid-session. `cache_control` breakpoints are placed deliberately at tier ends.
593 +2. **History is never rewritten.** Assistant blocks, tool results, and user messages are replayed byte-exact. `ContextCompacted` (and deterministic `ContextPruned` with a fixed placeholder string) are the only sanctioned breaks — both durable events, so every rebuild produces identical bytes.
594 +3. **Volatile context** (timestamps, git status, running-process lists, per-turn repository context) is injected **only into the API copy of the current user message**, never interleaved into history, never in the cached system tiers.
595 +4. **Model or instruction changes** mid-session (`ModelChanged`) start a new cache lineage; that is accepted and visible.
596 +5. Cache read/write tokens surface in `/cost` from real usage fields — cache health is observable, and a regression is a bug.
597 +
598 +---
599 +
600 +## 7. Model Runtime (ADR-10)
601 +
602 +```ts
603 +export interface ModelClient {
604 + stream(request: ModelRequest, signal: AbortSignal): AsyncIterable<ModelEvent>;
605 + countTokens?(request: ModelRequest): Promise<number>; // best-effort budgeting aid
606 +}
607 +export interface ModelRequest {
608 + model: string;
609 + system: SystemTier[]; // stable tiers with cache_control breakpoints
610 + messages: AnthropicMessage[]; // byte-stable projection (§6.5)
611 + tools: ToolSchema[];
612 + maxOutputTokens: number;
613 + thinking?: ThinkingConfig;
614 +}
615 +export type ModelEvent =
616 + | { type: "started"; requestId: string }
617 + | { type: "text-delta"; blockIndex: number; text: string }
618 + | { type: "thinking-delta"; blockIndex: number; text: string }
619 + | { type: "block-completed"; blockIndex: number; block: CompletedBlock } // text | thinking(+signature) | tool_use
620 + | { type: "finished"; stopReason: StopReason; usage: ModelUsage };
621 +export interface ModelUsage { // real API fields ONLY — never estimated (Rule #4)
622 + inputTokens: number; outputTokens: number;
623 + cacheReadTokens: number; cacheWriteTokens: number;
624 +}
625 +```
626 +
627 +**Error taxonomy** (typed, kernel branches on it):
628 +
629 +```ts
630 +export type ModelErrorKind =
631 + | "retryable" // 429 / 5xx / network — retried inside ModelClient with jittered
632 + // exponential backoff and a retry budget; surfaced only on exhaustion
633 + | "context-overflow" // routed to Context Engine (reactive compaction) — NEVER retried blindly
634 + | "auth" // fatal; actionable message; never logs the key
635 + | "invalid-request" // fatal; a KHAELOR bug — surfaced loudly
636 + | "cancelled"; // AbortSignal fired — not an error path, folds into Interrupted state
637 +export class ModelError extends Error {
638 + kind: ModelErrorKind; retryable: boolean; status?: number; requestId?: string;
639 +}
640 +```
641 +
642 +**Cancellation** is AbortSignal end to end: turn controller → SDK request → the async iterable throws `ModelError{kind:"cancelled"}`. No thread flags, no scattered checks (ADR-11).
643 +
644 +**Accounting:** `finished.usage` is the only source for `/cost` and compaction budgets. Two configured model ids share this one interface: `model` (main) and `auxModel` (compaction) — no provider framework, no second implementation.
645 +
646 +---
647 +
648 +## 8. Interruption & Steering (ADR-11)
649 +
650 +**Interrupt (`Esc`):**
651 +
652 +1. TUI dispatches `KernelCommand{interrupt}` → durable `Interrupted` event → `run.turnSignal.abort()`.
653 +2. The model stream aborts; in-flight tools receive the signal, get a 250 ms grace, then are cancelled.
654 +3. Every dangling `tool_use` is closed with a synthetic cancelled `tool_result` — recorded as durable `ToolCancelled` — so history is **protocol-valid at all times**.
655 +4. Background `process`-managed processes are **not** killed (explicitly long-lived).
656 +5. The loop's next `deriveNext` observes the interrupted state and returns; the session is intact and immediately usable.
657 +
658 +**Steering:** text typed while the agent works becomes a durable `SteeringQueued` event, shown as `Queued instruction`. The queue drains at exactly two seams — post-tool-batch and pre-model-call — recorded as `SteeringInjected` and appended into the API-copy content alongside tool results (never breaking role alternation). A long uninterrupted text stream cannot be steered until it completes or is cancelled — accepted price (ADR-11).
659 +
660 +---
661 +
662 +## 9. Completion & Verification (ADR-12)
663 +
664 +When the model stops (`end_turn`) and the `FileChangeSet` shows code changed this turn without fresh verification evidence, the gate:
665 +
666 +1. Detects relevant check commands from the repository (package scripts, test configs) — never blind full suites.
667 +2. Records `VerificationRequested{detectedChecks, attempt}` and **withholds the candidate answer** (preserved — budget exhaustion returns it rather than losing it).
668 +3. Injects a synthetic evidence-bearing nudge; max 2 attempts; documentation-only changes are filtered out.
669 +4. On acceptance records `TaskCompleted` carrying:
670 +
671 +```ts
672 +interface CompletionEvidence {
673 + objective: string;
674 + changedFiles: string[];
675 + checks: CheckResult[]; // command, exitCode, summary — real results only
676 + unresolvedIssues: string[];
677 +}
678 +```
679 +
680 +Completion is inferred from stop-without-tool-calls plus this gate (no finish tool in V1); if dogfooding shows ambiguity, an explicit finish signal is addable without kernel changes (ADR-12 concern, recorded).
681 +
682 +---
683 +
684 +## 10. Permissions (ADR-9) — placement summary
685 +
686 +Evaluation happens in exactly one place: the Tool Runtime, between `ToolRequested` and `ToolStarted`. Flow: capability derivation from tool input → hardline deny floor → last-match-wins rules → `allow` (durable `ToolApproved`) / `ask` (durable `PermissionRequested` → inline panel → `PermissionGranted{scope}` or `PermissionDenied`) / `deny`. "Always allow" persists a scoped rule to project config **and** records the grant event. Denial text returns to the model as the tool result observation (course correction, not dead end). "Silence is not consent": an unanswered request never auto-approves. Full model in the Phase 1 `PERMISSION_MODEL.md`.
687 +
688 +---
689 +
690 +## 11. Concurrency Model
691 +
692 +**One Node process, one event loop** (ADR-1, ADR-17). Concurrency is structured async, not threads.
693 +
694 +Concurrent at any moment:
695 +
696 +| Activity | Mechanism | Notes |
697 +|---|---|---|
698 +| Model stream consumption | async iterable | one at a time per session |
699 +| Tool execution | async, **sequential within a batch** in V1 | parallel read-only tools = post-V1 optimization, measured first |
700 +| Background processes | child processes + stream I/O | rolling buffers; outlive turns, not the process |
701 +| TUI input handling | stdin events | always responsive — never awaited behind engine work |
702 +| TUI rendering | 16 ms coalescer flush | bounded live region keeps render work small |
703 +| Log appends | serialized per-session write queue | write-ahead of publish |
704 +
705 +**Cancellation tree:** `session controller → turn controller → { model request, each tool execution }`. `Esc` aborts the turn controller only. `SIGINT`/quit aborts the session controller, stops background processes (`stopAll`), flushes the log, restores the terminal.
706 +
707 +**Recorded risk (ADR-17):** render work and tool I/O share the event loop. Mitigations: bounded live region + coalescing keep render slices small; the bus boundary is clean so moving the engine into a `worker_thread` later is a packaging change. If Phase 8 measurements show contention, that is the sanctioned escape hatch — measure first.
708 +
709 +**Ordering guarantee:** for a given session, durable events are appended and published in `seq` order; ephemeral deltas for a block are always delivered to the TUI before the durable event that settles that block (`EVENT_MODEL.md §7`).
710 +
711 +---
712 +
713 +## 12. Startup Sequence & Performance Budget
714 +
715 +### 12.1 Cold-start sequence
716 +
717 +```
718 +t0 node boots; cli entry parses argv (no framework, hand-rolled — zero-dep parse)
719 +t1 load config files (2 small JSON reads) + env; NO dotenv autoload; no network
720 +t2 initialize terminal + render the startup shell (header, prompt) — FIRST PAINT
721 + ── everything below is lazy / background ──
722 +bg git status + branch (async → fills status bar when ready)
723 +bg session store init (create/open lazily on first message or /resume)
724 +bg instruction-file discovery (KHAELOR.md etc.)
725 +lazy @anthropic-ai/sdk — imported on first model call
726 +lazy markdown/highlight/diff — imported on first render that needs them
727 +lazy ripgrep spawn — on first grep/glob/@-mention
728 +```
729 +
730 +The Anthropic key is validated on first use, not at startup (a missing key renders an actionable inline message, not a boot failure).
731 +
732 +### 12.2 Budgets (measured in CI where practical; ADR-1 lazy-import discipline)
733 +
734 +| Metric | Budget |
735 +|---|---|
736 +| Cold start → first paint, interactive prompt | **< 150 ms** |
737 +| Keystroke → echo (input latency) | **< 16 ms** |
738 +| Render frame during full-speed token stream | **< 16 ms** (one flush per frame) |
739 +| Tool dispatch overhead (gate + record, excl. tool work) | < 5 ms |
740 +| Session resume, 10K durable events | < 500 ms |
741 +| Memory, 4-hour session | bounded (capped live region, virtualized scrollback, rolling process buffers) |
742 +
743 +A startup benchmark script and an import-graph check (no heavy module in the boot path) are part of Phase 2's definition of done. No spinner-driven UX: status lines show real activity (`● Reading src/session/store.ts`), never fabricated progress (Absolute Rule #4).
744 +
745 +---
746 +
747 +## 13. V1 Non-Goals and Extension Seams (ADR-16, ADR-17)
748 +
749 +Explicitly **not** in V1 — with the seam that keeps each addable without kernel rewrites. Boundaries only; no premature abstraction beyond what is listed.
750 +
751 +| Post-V1 capability | V1 seam (already present) | What is NOT built now |
752 +|---|---|---|
753 +| Subagents | every event carries `sessionId`; tool registry is data-driven; child = new session with derived permissions | no task tool, no depth/budget machinery, no child UI |
754 +| Memory | `ContextEngine.selectContext` is the injection point; frozen-snapshot rule inherited from ADR-7 | no providers, no background review, no curation |
755 +| Skills | instruction-file discovery + data-driven registry | no skill format, loading, or self-generation |
756 +| MCP | `ToolDefinition`/`ToolRegistry` are plain data | no client, transport, or config surface |
757 +| Multi-provider | `ModelClient` is the single boundary | no adapter framework, no second implementation |
758 +| Docker/SSH remote | 4-method `Workspace`; strategy will be "run the core remotely", not per-syscall proxying | no `Workspace` implementations beyond local |
759 +| Shadow-git snapshots / revert | baselines + snapshot hashes representable as events | no shadow repo machinery |
760 +| Session branch/rewind | linear log; envelope reserves `parentId?` | no tree invariants |
761 +| Daemon / IDE / `khaelor serve` | the typed bus vocabulary is the only "API"; engine↔TUI boundary is command/event only | no server, sockets, or RPC |
762 +| `worker_thread` engine split | clean bus boundary (§11) | not until Phase 8 measurements demand it |
763 +
764 +Also not V1: OpenAI/Gemini/OpenRouter/local models, browser automation, computer vision, cloud execution, web UI, multi-user, plugins, marketplace (CLAUDE.md §23).
765 +
766 +---
767 +
768 +## 14. Cross-References
769 +
770 +- `docs/EVENT_MODEL.md` — normative event vocabulary, JSONL format, projection rules, coalescing contract, bus API.
771 +- `docs/research/KHAELOR_ARCHITECTURE_DECISIONS.md` — ADR-1…17 (binding rationale).
772 +- Phase 1 remaining deliverables: `TUI_DESIGN.md` (incl. the ADR-14 framework spike report), `TOOL_PROTOCOL.md`, `PERMISSION_MODEL.md`.
773 +
774 +---
775 +
776 +*Author: Simon-Pierre Boucher · contact@spboucher.ai*
added docs/DOGFOOD_NOTES.md +45 −0
Large diff (45 lines) is not loaded by default to keep the page fast.Load diff
added docs/EVENT_MODEL.md +607 −0
Large diff (607 lines) is not loaded by default to keep the page fast.Load diff
added docs/PERMISSION_MODEL.md +426 −0
Large diff (426 lines) is not loaded by default to keep the page fast.Load diff
added docs/TOOL_PROTOCOL.md +735 −0
Large diff (735 lines) is not loaded by default to keep the page fast.Load diff
added docs/TUI_DESIGN.md +951 −0
Large diff (951 lines) is not loaded by default to keep the page fast.Load diff
added docs/research/COMPARATIVE_ARCHITECTURE.md +214 −0
Large diff (214 lines) is not loaded by default to keep the page fast.Load diff
added docs/research/HERMES_ANALYSIS.md +350 −0
Large diff (350 lines) is not loaded by default to keep the page fast.Load diff
added docs/research/KHAELOR_ARCHITECTURE_DECISIONS.md +230 −0
Large diff (230 lines) is not loaded by default to keep the page fast.Load diff
added docs/research/MINI_SWE_ANALYSIS.md +220 −0
Large diff (220 lines) is not loaded by default to keep the page fast.Load diff
added docs/research/OPENCODE_ANALYSIS.md +371 −0
Large diff (371 lines) is not loaded by default to keep the page fast.Load diff
added docs/research/OPENHANDS_ANALYSIS.md +336 −0
Large diff (336 lines) is not loaded by default to keep the page fast.Load diff
added eslint.config.mjs +25 −0
Large diff (25 lines) is not loaded by default to keep the page fast.Load diff
added package-lock.json +3141 −0
Large diff (3,141 lines) is not loaded by default to keep the page fast.Load diff
added package.json +39 −0
Large diff (39 lines) is not loaded by default to keep the page fast.Load diff
added prototypes/tui-spike/RESULTS.md +128 −0
Large diff (128 lines) is not loaded by default to keep the page fast.Load diff
added prototypes/tui-spike/candidate-a/main.ts +229 −0
Large diff (229 lines) is not loaded by default to keep the page fast.Load diff
added prototypes/tui-spike/candidate-b/main.tsx +157 −0
Large diff (157 lines) is not loaded by default to keep the page fast.Load diff
added prototypes/tui-spike/driver/debug-ink-input.tsx +90 −0
Large diff (90 lines) is not loaded by default to keep the page fast.Load diff
added prototypes/tui-spike/driver/run.ts +116 −0
Large diff (116 lines) is not loaded by default to keep the page fast.Load diff
added prototypes/tui-spike/out/capture-a.txt +9706 −0
Large diff (9,706 lines) is not loaded by default to keep the page fast.Load diff

Diff truncated — file too large.

Menu

Theme