extended-rlm
by lukiies
README.md
# Extended RLM
**extended-rlm** is a self-learning knowledge layer for local LLM chat, built as a Model
Context Protocol (MCP) server. It has two faces:
1. **The engine** (this repository): a Python MCP server that answers questions from plain
Markdown files in a workspace, writes new knowledge back every turn, and stamps every answer
with an explicit grounding verdict. Retrieval follows recursive language model (RLM) research
([arXiv:2512.24601](https://arxiv.org/abs/2512.24601)): cheap grep-like pre-filtering plus
distillation by a small local "reader" model beats heavyweight RAG for project-scale corpora —
no vector store on the default path.
2. **A multi-agent knowledge stack**: the same engine started N times, once per knowledge
domain (a *specialised knowledge agent*, e.g. `extended-rlm-marketing`,
`extended-rlm-lawyer`), each with its own workspace/KB, all serving one LM Studio chat.
The chat model routes each question to the agent whose domain matches — or answers directly
when no domain matches.
- **Version:** 0.2.0 (see [CHANGELOG.md](CHANGELOG.md))
- **Licence:** MIT
- **Transport:** stdio (FastMCP) · **Package:** `src/extended_rlm/` · **Platform:** Windows-first
(PowerShell host scripts), engine itself is cross-platform Python 3.10+
---
## Repository map (what precisely is inside)
```
extended-rlm\
├── src\extended_rlm\ the engine (Python package)
│ ├── server.py MCP server: 20 tools, agent identity/suffixing, bootstrap
│ ├── config.py layered config: defaults < config.yaml < env vars
│ ├── search.py ripgrep/grep search, keyword extraction/expansion, triggers
│ ├── chunker.py header-aware ~500-token chunking
│ ├── ranker.py TF-IDF-like ranking (INDEX 1.5x / trigger 2.0x boosts)
│ ├── reader_client.py OpenAI-compatible reader client, grounded distillation,
│ │ response cache, no-think prefill, session token counters
│ ├── kb_writer.py atomic topic/memory writes, INDEX maintenance, dedup,
│ │ supersedes, procedural-rule mirroring (F1)
│ ├── rules_digest.py [MAIN]-tagged rules -> MAIN-RULES.md digest (~1000-token cap)
│ ├── enforce.py action-time gates from topic frontmatter (F4)
│ ├── health.py KB drift diagnosis, claim verification, write log
│ ├── stats_log.py per-agent usage JSONL + stack-wide per-agent/TOTAL summary
│ ├── files.py workspace-scoped file tools + soft delete (.deleted\)
│ └── fileserver.py standalone localhost download server (one per agent)
├── scripts\
│ ├── start-chat.ps1 one-command stack launcher (agents, reader, LM Studio,
│ │ system-prompt pinning, stats markers)
│ ├── stop-chat.ps1 session token summary (chat vs readers, per agent) + shutdown
│ ├── new-agent.ps1 create + register a new knowledge agent (scaffolds workspace)
│ └── watch-chat-speed.ps1 live tok/s monitor
├── host\lmstudio\ LM Studio host integration
│ ├── system-prompt.md condition-neutral bootstrap prompt (routing mandate +
│ │ get_session_rules_* bootstrap order)
│ ├── enable-layer.ps1 register one agent in ~\.lmstudio\mcp.json (env wiring)
│ ├── disable-layer.ps1 remove all extended-rlm* entries (OFF condition)
│ ├── setup-filesystem.ps1 filesystem MCP server (present in both conditions)
│ ├── chat-model-load.config.json pinned chat-model load config (context/seed/KV cache)
│ └── README.md step-by-step Windows host setup
├── COMMON-RULES.md engine-wide behaviour rules served to EVERY agent's chat
│ session (routing, query-first, capture, stats duties)
├── extended-rlm.json stack config: chat model, reader backend, agent registry
├── docs\
│ ├── creating-a-new-agent.md agent lifecycle guide (scaffold -> seed -> verify)
│ ├── authoring-erlm-knowledge.md FULL spec for authoring a new agent's knowledge
│ │ data (for frontier-LLM KB generation)
│ ├── msc-experiment-environment.md experiment configuration record (pre-freeze)
│ ├── EVALUATION-RUN-GUIDE.md ON/OFF evaluation procedure
│ ├── GROUNDING_AND_HIERARCHY.md grounding-verdict contract
│ ├── RECONCILIATION-recorded-retrievable-enforced.md F1-F4 design note
│ ├── KNOWLEDGE_BASE_SETUP_GUIDE.md turning any workspace into a structured KB
│ └── WEBSITE_GUIDE.md optional docs website from the KB
├── tests\ 182 tests, no network / no live reader needed
├── examples\ RULES.example.md, .env.example
├── config.example.yaml per-workspace tuning template
└── start_server.bat single-server launcher (legacy/bare-engine mode)
```
A **knowledge workspace** (one per agent) lives OUTSIDE this repo and contains only data:
`AGENT.md` (domain + routing lists), `RULES.md` (strict rules), `PRINCIPLES.md` (behaviour
principles), auto-generated `MAIN-RULES.md` (session digest), and `.kb\` (INDEX.md, topics\,
code_examples\, memory\). The complete authoring specification is
[docs/authoring-erlm-knowledge.md](docs/authoring-erlm-knowledge.md).
---
## MCP tool surface (20 tools per agent)
In multi-agent mode every tool name gets the agent suffix (`ask_knowledge_base_lawyer`, …) and
its description is prefixed with `[agent: <name> | domain: <domain>]` — the routing signal.
### Session bootstrap
| Tool | Purpose |
|---|---|
| `get_session_rules()` | Binding session rules: the agent's `AGENT.md` + engine `COMMON-RULES.md` + the `MAIN-RULES.md` digest. Called once per agent at chat start. Marks the session as bootstrapped (see safety net below). |
### Retrieval
| Tool | Purpose |
|---|---|
| `ask_knowledge_base(query, context?)` | MANDATORY first step for any in-domain question: grounded answer with `Source N` citations, `Grounding:` verdict, reader token stats line. Carries a `⚠ SESSION RULES NOT LOADED` banner if called before `get_session_rules`. |
| `list_knowledge_base()` | KB structure (files, sizes, Tier-1 memory, reader status). |
| `clear_knowledge_cache()` | Clear the reader response cache. |
| `get_kb_session_stats()` | Session token usage for ALL connected agents + TOTAL (computed from the shared stats dir — one call covers the whole stack). |
| `reset_kb_session_stats()` | Reset this agent's in-memory counters. |
### Writing (self-learning)
| Tool | Purpose |
|---|---|
| `update_knowledge_base(topic, content, ...)` | Atomic topic write + INDEX row, dedup, `supersedes`, `triggers`, enforce-gate frontmatter, verify-on-write. |
| `record_memory(kind, name, content, ...)` | Tier-1 behavioural memory (`feedback`/`project`/`user`) + MEMORY.md index; procedural rules auto-mirrored into retrievable topics (F1). |
| `record_rule(kind, text, main)` | Append a strict rule (→ RULES.md) or behaviour principle (→ PRINCIPLES.md); `main=True` tags it `[MAIN]`; regenerates the session digest and reports budget %. |
### Enforcement, health, accuracy
| Tool | Purpose |
|---|---|
| `applicable_rules(file_path?, change_kind?, query?)` | Action-time gates from topic `enforce_globs`/`enforce_kinds` frontmatter, with a grounded semantic fallback. |
| `kb_health_check()` | KB drift: oversized topics, INDEX orphans/broken rows, dead links, staleness. |
| `reader_health()` | Live-ping the reader endpoint; concrete failure reason on error. |
| `verify_kb_claim(file, snippet, line_hint?)` | Confirm a cited file+snippet still exists (stale-KB detector). |
| `get_recent_sessions_summary(days)` | Recent KB-related git activity. |
| `kb_update_digest()` | KB writes since last digest + the stack-wide token block (end-of-turn summary). |
### Workspace files
| Tool | Purpose |
|---|---|
| `list_workspace_files(subdir?)` / `read_workspace_file(path)` / `write_workspace_file(path, content, mode)` | Workspace-scoped file access; writes to RULES.md/PRINCIPLES.md auto-regenerate the MAIN digest. |
| `delete_workspace_file(path)` | Soft delete — moved to `.deleted\<timestamp>\`, always undoable. |
| `get_download_link(path)` | Clickable localhost download link served by the per-agent fileserver. |
---
## Architecture
```
LM Studio chat (one local model, e.g. Qwen3.5-9B)
│ system prompt (pinned into model defaults by start-chat.ps1):
│ bootstrap order + routing mandate
│
├── MCP: extended-rlm-marketing ──► workspace A (own RULES/PRINCIPLES/.kb)
├── MCP: extended-rlm-lawyer ──► workspace B │
├── MCP: <any further agents> ──► workspace ... │ all agents share
└── MCP: filesystem (workspace file access) ▼ ONE reader:
small local LLM (Ollama or the
LM Studio model itself) distills
top-ranked chunks into grounded,
cited answers
```
Per query: keyword extraction/expansion → ripgrep across the knowledge tiers →
header-aware chunking (+ trigger-matched topics) → ranking (INDEX/trigger boosts) →
reader distillation → answer + `Grounding: GROUNDED | PARTIAL | NOT-FOUND` + token stats.
### Session bootstrap & the [MAIN] rules digest
- `RULES.md` (strict rules) and `PRINCIPLES.md` (behaviour principles) are canonical and
unlimited; the agent updates them at runtime (`record_rule`, direct edits).
- Entries/sections tagged `[MAIN]` are compiled — deterministically, no LLM — into
`MAIN-RULES.md`, hard-capped at **~1000 tokens**. Only this digest (plus `AGENT.md` and
`COMMON-RULES.md`) is loaded into chat sessions; everything untagged stays retrievable on
demand. At ≥90% budget the engine demands consolidation: move a cohesive rule set into a KB
guideline topic, leave a one-line `[MAIN]` summary + pointer.
- Routing failure modes are defended on three channels: the system prompt is **pinned into the
chat model's LM Studio defaults** (every new chat gets it), the `ask_knowledge_base`
description itself declares the call mandatory for in-domain questions, and KB answers
produced before the bootstrap carry a corrective banner.
### Token accounting
Every reader call is appended to `logs\reader-usage-<agent>.jsonl` (shared stats dir, session
marker from `start-chat.ps1`). Any single agent's `get_kb_session_stats` therefore reports
**every** agent + TOTAL — one call per turn, no arithmetic for the chat model, robust to MCP
process restarts. `stop-chat.ps1` prints the full session summary (chat model vs readers,
per agent, cache hits) from LM Studio engine logs + the JSONL files.
### The grounding verdict contract
| Verdict | Meaning | Calling model's action |
|---|---|---|
| `GROUNDED` | Fully answered from the KB | Rely on it; do not re-derive |
| `PARTIAL` | Some parts `NOT IN KB` | Escalate only the missing parts: files, then cited web |
| `NOT-FOUND` | Nothing relevant | Escalate whole question, then capture the result into the KB |
### The self-learning loop (F1–F4): recorded, retrievable, enforced
| # | Guarantee | Mechanism |
|---|---|---|
| F1 | Procedural rules become retrievable | procedural `record_memory` auto-mirrored into a topic with seeded `triggers` |
| F2 | Retrieval survives re-phrasing | `triggers:` frontmatter indexed + boosted (2.0×) |
| F3 | Recorded ⇒ confirmed retrievable | verify-on-write re-runs the real pipeline for a paraphrase; warns `RECORDED-BUT-NOT-RETRIEVABLE` |
| F4 | Action-time enforcement | `applicable_rules` fires topics whose `enforce_globs`/`enforce_kinds` match the edit |
---
## Quick start (multi-agent stack, Windows + LM Studio)
```powershell
conda create -n GenAI_FA python=3.12; conda activate GenAI_FA
git clone <this-repository> extended-rlm; cd extended-rlm
pip install -e .[dev]
# 1. Declare agents in extended-rlm.json (or create one interactively):
.\scripts\new-agent.ps1 -Name finance -Domain "Corporate finance for ...: budgeting, cash-flow, ..."
# 2. Launch everything (mcp.json, workspaces, reader, LM Studio, system prompt):
.\scripts\start-chat.ps1
# 3. In LM Studio: enable the agents + 'filesystem' in the chat's Program panel. Chat.
# 4. End the session with the token summary:
.\scripts\stop-chat.ps1
```
Single-workspace (bare engine, canonical tool names): `start_server.bat --path C:\path\to\ws`
or `.\scripts\start-chat.ps1 -Workspace C:\path\to\ws`.
---
## Configuration
Resolution order: built-in defaults → workspace `config.yaml`
([template](config.example.yaml)) → environment variables. Secrets only via environment.
Key environment variables (full list in `src/extended_rlm/config.py`):
| Variable | Default | Purpose |
|---|---|---|
| `KNOWLEDGE_BASE_PATH` | cwd | Workspace root (equivalent to `--path`) |
| `RLM_AGENT_NAME` / `RLM_AGENT_DOMAIN` | unset | Agent identity; tool-name suffix + description tag (AGENT.md `Domain:` overrides the env domain) |
| `RLM_COMMON_RULES` | unset | Path to the engine-wide COMMON-RULES.md served by `get_session_rules` |
| `RLM_STATS_LOG` | unset | Per-agent usage JSONL; its parent dir is the shared stack-stats dir |
| `RLM_FILE_PORT` | auto | Stable download-server port for this agent |
| `RLM_READER_MODEL` / `READER_BASE_URL` / `READER_API_KEY` | `qwen3:4b` / Ollama / unset | Reader endpoint (any OpenAI-compatible `/chat/completions`) |
| `RLM_READER_ENABLED` | `1` | `0` → raw ranked chunks (no distillation) |
| `RLM_READER_NOTHINK` | unset | Suppress Qwen3/3.5 reader thinking via assistant prefill (fast path) |
| `RLM_READER_THINK_ALLOWANCE` / `RLM_READER_TIMEOUT` | `0` / `120` | Reasoning-reader headroom / HTTP timeout (s) |
| `RLM_RETRIEVAL_MODE` | `grep` | `grep` or `hybrid` (opt-in embeddings: `pip install -e .[embeddings]`) |
| `RLM_GROUNDING_ENABLED` / `RLM_GROUNDING_STRICTNESS` | `1` / `strict` | Verdict line behaviour |
| `RLM_MEMORY_SEARCH` / `MEMORY_DIR` | `1` / `<ws>\.kb\memory` | Tier-1 memory search |
| `RLM_ENFORCEMENT_ENABLED` / `RLM_ENFORCE_SEMANTIC` | `1` / `1` | Action-time gates |
| `RLM_CONFIG` | unset | Explicit config YAML path |
Stack-level settings (chat model + pinned load config, reader backend `ollama`/`lmstudio`,
agent registry) live in [`extended-rlm.json`](extended-rlm.json) and are applied by
`start-chat.ps1`.
---
## Evaluation: the ON/OFF switch
Designed for a controlled experiment (MSc project): same base model, same workspaces, same
prompts and sampler settings; the single manipulated factor is the set of `extended-rlm*`
entries in `mcp.json`.
- **ON:** `start-chat.ps1` (default) registers the configured agents.
- **OFF:** `start-chat.ps1 -Condition OFF` removes them; the model runs stock (filesystem MCP
stays in both conditions).
- The system prompt is condition-neutral and pinned into the model defaults; the pinned load
config freezes context length, seed, and KV-cache quantisation.
- **Measures:** task correctness, cross-session consistency, rule-following, factual accuracy,
token cost (per-answer stats line, `get_kb_session_stats`, `stop-chat.ps1` summary).
- Full procedure: [docs/EVALUATION-RUN-GUIDE.md](docs/EVALUATION-RUN-GUIDE.md); environment
record: [docs/msc-experiment-environment.md](docs/msc-experiment-environment.md).
---
## Tests
```powershell
pytest # 182 tests, no network, no live reader needed
ruff check src/
```
Distillation, grounding, verify-on-write, enforcement, the [MAIN] digest, stats aggregation,
bootstrap banner, and file tools are all exercised with fake clients and pure functions.
## Security notes
- Secrets (`READER_API_KEY`) come from the environment only; never from config files.
- The engine writes only inside the knowledge tiers (`RULES.md`, `PRINCIPLES.md`, `.kb\`,
memory dir) and the workspace file tools are path-jailed to the workspace (soft delete only).
- The download fileserver binds to `127.0.0.1` and serves only workspace files.
## Licence
MIT, see [LICENSE](LICENSE).
## Credits
- Recursive language model (RLM) research: [arXiv:2512.24601](https://arxiv.org/abs/2512.24601)
- [MCP Python SDK / FastMCP](https://github.com/modelcontextprotocol/python-sdk)
This server cannot be deployed
Maintenance
ActivitySlowing
ResponsivenessNo issues