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:

  1. Bayesian score — Patterns with higher scores are seeded with higher activation
  2. Embedding similarity — Cosine similarity between the query embedding and pattern embeddings (768-dim, via Ollama). Patterns above 0.3 similarity are included.
  3. 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)