Architecture
kli is a single Common Lisp binary built with SBCL. One process handles the MCP server, hook dispatch, the dashboard, and project initialization.
Claude Code <--http--> kli serve task graph + pattern learning
Claude Code ---calls--> kli hook <event> session, tool, conflict hooks
Browser <--http--> kli dashboard web UI on :9091
Developer ---runs---> kli init project setup
Event Sourcing
Each task is an append-only event log (events.jsonl). There is no mutable database — state is computed by replaying events through CRDT merge functions.
This design gives you:
- Consistency — Vector clocks establish causal order. Concurrent events from different sessions merge deterministically.
- Auditability — Every observation, edge change, and metadata update is preserved with its timestamp and session ID. Nothing is overwritten.
- Multi-agent safety — OR-Sets handle edge add/remove, LWW-Registers handle metadata, PN-Counters handle feedback scores. Concurrent writes resolve without coordination locks.
CRDT Types
| CRDT | Used for | Merge semantics |
|---|---|---|
| G-Set | Observations, session joins | Union — additions never conflict |
| OR-Set | Graph edges | Add/remove with unique tags; concurrent add + remove preserves the add |
| LWW-Register | Metadata values, task status | Last writer wins by vector clock comparison |
| LWW-Map | Task metadata as a whole | Per-key LWW-Register semantics |
| PN-Counter | Helpful/harmful feedback scores | Increment/decrement merge independently |
| Vector Clock | Causal ordering | Component-wise max |
Event Types
session.join — A session started working on this task
session.claim — A session took exclusive ownership (optional)
observation — Knowledge captured during work
task.complete — Task marked as finished
task.reopen — Completed task reopened for further work
metadata.set — Key-value metadata updated
edge.add — Graph edge created (phase-of, depends-on, etc.)
edge.remove — Graph edge severed
handoff.create — Handoff document generated for session transfer
Pattern Scoring
Patterns carry helpful and harmful feedback counters. The ranking score uses a Bayesian Beta-Binomial posterior mean:
score = (h + 1) / (h + m + 2)
Where h = helpful count, m = harmful count. This gives:
| State | Score | Meaning |
|---|---|---|
| h=0, m=0 | 0.50 | Uncertain — new pattern, no evidence |
| h=5, m=0 | 0.86 | Likely helpful — consistent positive feedback |
| h=16, m=0 | 0.94 | Proven — high confidence |
| h=1, m=2 | 0.40 | Likely harmful — more negative than positive evidence |
The Beta-Binomial model handles small sample sizes gracefully. A pattern with 1 helpful vote isn't treated as 100% reliable — it scores 0.67, reflecting genuine uncertainty. Confidence grows with evidence.
Pattern Retrieval
Retrieval combines three signals:
- Bayesian score — Patterns with higher scores are seeded with higher activation
- Embedding similarity — Cosine similarity between the query embedding and pattern embeddings (768-dim, via Ollama). Patterns above 0.3 similarity are included.
- Graph spread activation — Activation spreads along edges in the pattern co-application graph. Patterns that co-occur with already-activated patterns receive a boost (10% of edge weight times the activating pattern's score).
Query Languages
TQ (Task Queries)
TQ is a pipeline-based query language for the task graph. Queries are S-expressions — they parse, they don't eval. No code execution, no injection surface.
Sources start a pipeline:
:all ;; All tasks
(active) ;; Tasks with event logs
(current) ;; Current task
(node "pattern") ;; Tasks matching a substring
(query "plan-ready") ;; Named query (ready phases)
Steps transform the pipeline:
(:where predicate) ;; Filter
(:sort :field) ;; Sort descending
(:take n) ;; Limit
:enrich ;; Load full CRDT state
(:select :field1 :field2) ;; Project fields
(:group-by :field) ;; Group
(:follow :edge-type) ;; Traverse edges forward
(:back :edge-type) ;; Traverse edges backward
:ids ;; Extract IDs
:count ;; Count
Mutations modify tasks in the pipeline:
(:complete!) ;; Mark complete
(:reopen!) ;; Reopen
(:observe! "text") ;; Add observation
(:set-meta! :key "value") ;; Set metadata
(:link! "target" :edge-type) ;; Add edge
(:sever! "target" :edge-type) ;; Remove edge
(:sever-from-parent! :type) ;; Bulk sever from parent
Scaffolding creates phased plans:
(scaffold-plan!
(p1 "Design schema"
:objective "Normalize user tables"
:acceptance "Migration passes, tables exist"
:steps "1. Define schema\n2. Run migrations\n3. Verify")
(p2 "Implement API" :after p1
:objective "REST endpoints for CRUD"
:acceptance "All endpoints return 200")
(p3 "Write tests" :after p2))
Phase metadata keywords: :objective, :acceptance, :steps, :context, :constraints.
PQ (Pattern Queries)
PQ queries the pattern graph with the same pipeline syntax:
;; All patterns
(-> :all :count)
;; Patterns proven helpful
(-> (proven :min 3) (:take 5))
;; Semantic + graph activation for current context
(-> (activate "CRDT conflict resolution" :boost (lisp nix)) (:take 3))
;; Search by content
(-> (search "testing strategy") (:take 5))
;; Give feedback
(-> (pattern "lisp-042") (:feedback! :helpful "Confirmed in REPL"))
Libraries
| Library | Description |
|---|---|
lib/crdt | G-Set, OR-Set, PN-Counter, LWW-Register, LWW-Map, Vector Clock |
lib/task | Event log, CRDT state, DAG algorithms, TQ engine, Markov clustering |
lib/playbook | PQ engine, pattern store, helpful/harmful scoring, activation graph |
lib/mcp-framework | MCP server framework: JSON-RPC 2.0, tool registry, schema generation |
lib/mcp-http | HTTP+SSE transport with session management |
lol-reactive | Reactive web framework for the dashboard (flake input — HTMX, signals, Tailwind) |
lib/claude-hooks | Claude Code hook handlers (session lifecycle, tool tracking, conflict detection) |
Building
kli uses Nix for reproducible builds. The standalone flake at kli/flake.nix pins all dependencies including cl-deps (Common Lisp package set) and lol-reactive.
nix build # produces result/bin/kli
nix run # run directly
nix flake check # run test suites (CRDT, task, playbook, hooks)
From source with SBCL and Quicklisp:
(asdf:load-system :kli)
(kli:main)