LIM (Local Inference Manager) is a C++ terminal-based LLM controller built on llama.cpp. It provides a persistent, stateful session where the KV-cache is never discarded: each turn's tokens are simply appended, so new input costs only O(input tokens), not O(total history). LIM has native filesystem tools for reading, searching, editing, and writing files, plus web searching and PDF reading.
Every mainstream chatbot (and most local LLM frontends using server APIs) follows the reprocess-every-turn model: each time you send a message, the entire conversation history is re-transmitted in full and re-tokenized from scratch. The per-turn decode cost grows linearly with context length, making long conversations progressively slower.
LIM avoids this by design: it runs locally as a single persistent process where the KV-cache is never discarded. Each turn simply continues from where the last one left off. This is the same approach used by llama-cli in interactive mode. LIM includes an integrated tool system, session save/restore, interactive undo, and LaTeX-aware browser output.
+-------------+ +---------------+ +---------------+
| Your TTY |<--->| lim (C++) |<--->| llama.cpp |
| (readline) | | controller | | inference |
+-------------+ +-------+-------+ +---------------+
|
+-------------+-------------+
| | |
+-----v-----+ +----v-----+ +-----v------+
| Filesystem| | Web | | Browser |
| Tools | | Search | | (FIFO) |
| | | & PDFs | | |
+-----------+ +----------+ +------------+
lim: The C++ binary. Handles the REPL, KV-cache management, tool dispatch, and signal handling.limServer.py: An optional Python WebSocket server that streams output to a browser via/tmp/lim.fifo.
- A GPU with CUDA support (NVIDIA recommended) and the CUDA toolkit installed.
For CPU-only builds, run
make GGML_CUDA=off. - Python 3 with
aiohttpfor the browser server:pip3 install aiohttp. - A GGUF model file (e.g., Qwen, Llama, Mistral).
- Optional: SearXNG for web search and Docling for PDF reading. LIM auto-starts them on demand; override the commands with
LIM_SEARXNG_CMD/LIM_DOCLING_CMD(see Web Search & PDF Setup below).
Note: llama.cpp is bundled as a git subrepo with a LIM-specific patch for recurrent state checkpointing (required for instant
/undoon hybrid models). The patched code is built automatically by the Makefile. A PR upstream to llama.cpp is pending.
git clone https://github.com/statefullm/lim.git
cd lim
make
./lim --helpThe build will automatically detect your GPU (if any) and compile llama with the appropriate architecture flags. No manual setup is required.
All auto-detected values can be overridden via environment variables:
| Variable | Purpose | Example |
|---|---|---|
CUDA_ARCH_FLAGS |
Override GPU architecture detection | make CUDA_ARCH_FLAGS=120a |
LIM_LLAMA_BUILD_DIR |
Use pre-built llama from external directory | export LIM_LLAMA_BUILD_DIR=/path/to/llama/build |
LIM_LLAMA_CMAKE_FLAGS |
Extra cmake flags passed to the llama build | make LIM_LLAMA_CMAKE_FLAGS="-DGGML_AVX512=off" |
GGML_CUDA |
Force CUDA on/off | make GGML_CUDA=off for CPU-only |
GGML_HIPBLAS |
Enable ROCm/HIP build | make GGML_HIPBLAS=on |
To clean only the llama build artifacts without touching lim:
make llama-cleanAfter building, install the binary and config files. This must be run as $LIM_AI_USER (e.g., su - $LIM_AI_USER) so that files are installed into that user's home directory, which is where LIM will look for them at runtime:
su - $LIM_AI_USER
make installThis installs:
limbinary to~/bin/lim(ensure~/binis in your PATH via.bashrc)- System prompt to
~/.config/lim/prompt - Reincarnate instructions to
~/.config/lim/reincarnate - Search cache directory at
~/.config/lim/searchCache - Browser server files (
limServer.py,viewer.html,libs/) to~/.config/lim/
All LIM configuration files live under ~/.config/lim/ by default. Override with the LIM_CONFIG_DIR environment variable (set in $LIM_AI_USER's .bashrc) to place them elsewhere:
# As $LIM_AI_USER
export LIM_CONFIG_DIR=/opt/lim/config
make installThe LIM_CONFIG_DIR is read at runtime, so no recompilation is needed.
The dedicated LLM user is controlled by the LIM_AI_USER environment variable (defaults to ai). All references below use $LIM_AI_USER. The group name always matches the user name.
LIM must run as a dedicated user. The binary enforces this at startup: it checks getuid() and refuses to run otherwise. This is a security measure: the LLM has filesystem write access and shell execution capabilities, so isolating it behind a dedicated user limits blast radius.
Create the user and group if they don't exist:
export LIM_AI_USER=ai
sudo groupadd -f $LIM_AI_USER
sudo useradd -m -g $LIM_AI_USER -s /bin/bash $LIM_AI_USERThen add your personal user to the $LIM_AI_USER group so you can read and write files the LLM creates without needing sudo chown:
sudo usermod -aG $LIM_AI_USER $USERLog out and back in (or run newgrp $LIM_AI_USER) for the group change to take effect.
By default, Git refuses to operate in repositories owned by a different user. Since LIM runs as $LIM_AI_USER but your project directories are owned by you, add the [safe] section to your personal ~/.gitconfig:
[safe]
directory = /home/$LIM_AI_USERThis prevents "fatal: detected dubious ownership in repository" errors when the LLM runs git commands.
Your project directories should be group-writable by the $LIM_AI_USER group so the LLM can read and write files. Because you added your personal user to the $LIM_AI_USER group (step 1), access is symmetric: you can read files the LLM creates, and the LLM can read your files. The recommended layout for /home/$LIM_AI_USER:
$ ls -ld /home/$LIM_AI_USER
drwxrwsr-x. 42 $USER $LIM_AI_USER 4096 Jan 1 00:00 /home/$LIM_AI_USER/The setgid bit (s) ensures new files inherit the $LIM_AI_USER group.
To set permissions in any project sandbox, copy aishare from this repository to your personal /home/$USER/bin/:
cp aishare /home/$USER/bin/Then run it on any directory you want the AI to access:
aishare /home/$LIM_AI_USER/projectIt sets ownership to $USER:$LIM_AI_USER, grants group read/write and setgid, and clears the sticky bit. When run on /home/$LIM_AI_USER, it fixes permissions on the home directory itself plus all top-level entries, skipping .ssh.
LIM must be run as user $LIM_AI_USER. The simplest approach is to SSH into the host as that user, then launch lim directly. This keeps the workflow consistent whether you're using VS Code or a standalone terminal.
Add this to the ~/.bashrc of $LIM_AI_USER:
alias coder='lim ~/models/Qwen3.6-27B-UD-Q5_K_XL.gguf'Replace the model path with whichever GGUF you want to use. Then, before running coder, connect to the LIM server $LIM_HOST:
ssh $LIM_AI_USER@$LIM_HOST
coderIf running locally (no SSH needed), switch to the user first:
su - $LIM_AI_USER
coderLIM automatically detects P-cores and E-cores on hybrid CPUs (Intel Alder Lake, Raptor Lake, etc.) by comparing thread_siblings_list from sysfs. It pins background services accordingly:
| Workload | Default Cores | Description |
|---|---|---|
| SearxNG search server | E-cores | Light background Python process |
| Browser WebSocket server | E-cores | Light background Python process |
| Docling PDF converter | P-cores | Heavy ML inference workload |
On non-hybrid CPUs (all cores identical), no taskset pinning is applied. If the pinning command isn't found on $PATH (e.g., macOS has no taskset), pinning is silently skipped.
Override the auto-detected layout with LIM_TASKSET:
# Format: LIM_TASKSET="P_CORES:E_CORES"
export LIM_TASKSET="0-15:16-23" # Classic i9-12900K layout
export LIM_TASKSET="0-7:" # P-cores 0-7, no E-core pinning
export LIM_TASKSET=":8-15" # No P-core pinning, E-cores 8-15
export LIM_TASKSET="::" # Disable all taskset pinningmacOS / non-Linux systems: macOS has no taskset binary. To enable manual core pinning, install an alternative and point LIM_TASKSET_CMD at it:
# Install numactl via Homebrew, then use it as the pinning backend
brew install numactl
export LIM_TASKSET="0-3:4-7"
export LIM_TASKSET_CMD="numactl --cpunodebind"Or write a custom wrapper script and set LIM_TASKSET_CMD to its path. If the command isn't on $PATH, pinning is silently skipped -- services still start normally, just unpinned.
The detected topology and pinning status are logged to stderr at startup.
Add these lines to /home/$LIM_AI_USER/.bashrc:
# --- /home/$LIM_AI_USER/.bashrc ---
umask 0002 # Group-writable files by default
# Track current working directory so the LLM knows where you are.
cd() {
builtin cd "$@" && pwd > $HOME/.cwd
}
# Block git add -A / --all and git add . to prevent the LLM from staging untracked files
git() {
if [ "$1" = "add" ]; then
for arg in "$@"; do
if [ "$arg" = "-A" ] || [ "$arg" = "--all" ] || [[ "$arg" =~ ^-[a-zA-Z]*A[a-zA-Z]*$ ]] || [ "$arg" = "." ]; then
echo "ERROR: git add -A / --all / . is disabled: use git commit -a or specify files explicitly" >&2
return 1
fi
done
fi
command git "$@"
}
# Optional: add custom paths
export PATH="$HOME/bin:$PATH"The cd override writes the current directory to $HOME/.cwd, which LIM reads at startup so it knows your working directory. The umask 0002 ensures files created by $LIM_AI_USER are group-readable/writable.
The system prompt lives at ~/.config/lim/prompt. A default prompt file ships with this repository and is installed to that location by make install. This file is read once at startup and baked into the KV-cache. It defines the LLM's behavior: available tools, editing workflow, formatting rules, etc. You can customize it for different use cases (coding assistant, writer, researcher, etc.).
Create /home/$LIM_AI_USER/.lim_aliases to define shorthand expansions at the >>> prompt:
# ~/.lim_aliases: one key=value per line; # comments are ignored
# Keys must start with / to distinguish them from regular messages
/test=Test the filesystem tools and clean up after.
/message=Show me a 1-sentence concise git commit message relative to HEAD.
/commit=run git commit -a -m "<message>"
/make=run make
/amend=Amend the previous commit.
/remind=Read the system prompt from ~/.config/lim/prompt and follow it strictly!
/prompt=Follow the system prompt strictly!
/tools=Use our robust filesystem tools.
At the >>> prompt, typing /commit expands to run git commit -a -m "<message>" before being sent to the LLM. Lines starting with # are treated as comments. Blank lines are skipped. Built-in command names (e.g., /quit, /clear) cannot be used as alias keys. This is loaded fresh each session.
Set via LIM_OUTPUT:
| Value | stdout | Browser | Description |
|---|---|---|---|
| 3 | ✅ | ✅ | Both stdout and browser |
| 2 (default) | ❌ | ✅ | Browser only |
| 1 | ✅ | ❌ | Stdout only |
| 0 | ❌ | ❌ | No output (system stderr still works) |
| Variable | Default | Description |
|---|---|---|
LIM_AI_USER |
ai |
Username that LIM must run as. Used by the binary for the user check, and by the VS Code extension for SSH. |
LIM_CACHE_DIR |
.cache |
Directory (relative to CWD) for fast restore KV-cache files. |
LIM_CONFIG_DIR |
~/.config/lim |
Directory for LIM config and server files (prompt, reincarnate, userprompt, searchCache, limServer.py, viewer.html, libs/). The C++ binary launches python3 $LIM_CONFIG_DIR/limServer.py when browser output is enabled. Override to place everything elsewhere. |
LIM_LOG_DIR |
log |
Directory (relative to CWD) for session logs, token traces, and autosave files. |
LIM_SAVE_DIR |
. |
Directory prepended to relative paths in /save, /load, /delete, and the CLI restore argument. Absolute paths (starting with /) are unaffected. |
LIM_HOST |
unset | Hostname or IP of your LIM server. Used for SSH connection and browser viewer URL. |
LIM_PORT |
8765 |
Port for the browser WebSocket server |
LIM_OUTPUT |
2 |
Output mode: 0 = none, 1 = stdout only, 2 = browser only (default), 3 = both |
LIM_VIEWER_URL |
(auto) | Override the auto-generated viewer URL |
LIM_WEB_CONTEXT_FRACTION |
0.75 |
Fraction of LIM_CTX reserved for fetched web content budget. The per-file limit (LIM_WEB_FILE_MAX) defaults to 25% of this budget, so ~3-4 files fill it. Set between 0.0 and 1.0. |
LIM_WEB_FILE_MAX |
(auto) | Max characters per fetched file before middle-drop truncation. Defaults to 25% of the session web budget (LIM_CTX * LIM_WEB_CONTEXT_FRACTION * 4). Set explicitly to override. |
LIM_WEB_HTML_MAX |
500000 |
Max bytes to buffer when downloading HTML/text pages via curl |
LIM_WEB_PDF_MAX |
50000000 |
Max bytes to buffer when downloading PDFs via curl (50 MB) |
LIM_WEB_TIMEOUT |
600 |
HTTP request timeout in seconds for fetches and searches |
LIM_SEARCH_COOLDOWN |
3 |
Minimum seconds between web searches to avoid rate-limiting SearxNG |
LIM_DOCLING_CMD |
~/venv/bin/docling-serve run --enable-ui |
Command to start the Docling PDF service. Override if installed elsewhere (e.g., via Docker or a different venv). |
LIM_SEARXNG_CMD |
cd ~/searxng && python -m searx.webapp |
Command to start the SearxNG search service. Override if installed elsewhere. |
LIM_DEBUG |
0 |
Set to 1 for verbose token-level logging in $LIM_LOG_DIR/<N>.tokens |
LIM_EOG_RESAMPLE_MAX |
64 |
Maximum resampling attempts when a spurious EOG is detected. When the model emits an EOG token but hasn't finished its response, LIM resamples up to this many times trying to recover a non-EOG token. Increase if you see premature turn endings with Qwen3.6 or similar models. |
LIM_GPU_LAYERS |
-1 |
Number of layers offloaded to GPU (-1 = auto-fit all layers). When set explicitly, bypasses auto-fitting. For MoE models that exceed VRAM, auto-fit uses partial layer offloading (dense weights on GPU, sparse expert weights on CPU) for optimal throughput. |
LIM_HONEST_SPEED |
0 |
Set to 1 for "honest" wall-clock speed diagnostic -- tokens / total generate() call time, including all CPU-side overhead (sampling, output rendering, tool-call detection). Default 0 reports generation time matching llama-cli's "Generation: X t/s" -- measured from first-token sample+sync to last-token sample+sync, covering N sampling operations and (N-1) decode cycles. |
LIM_SPEED_INTERVAL |
100 |
Number of tokens between in-loop speed diagnostic updates. |
LIM_USE_MLOCK |
1 |
Lock model in RAM to prevent swapping |
LIM_USE_MMAP |
0 |
Use memory-mapped model loading (faster startup, more RAM pressure) |
LIM_BATCH |
2048 |
Batch size for token feeding |
LIM_CTX |
262144 |
Context window size (KV-cache token capacity) |
LIM_THREADS |
(auto) | Threads for inference (physical core count) |
LIM_THREADS_BATCH |
(auto) | Threads for batch processing (physical core count) |
LIM_UBATCH |
512 |
Unbatched size |
LIM_MIN_P |
0.0 |
Minimum probability threshold: keep tokens where P >= min_p * P(top) |
LIM_PENALTY_FREQ |
0.0 |
Frequency penalty: discourages overused tokens proportional to frequency |
LIM_PENALTY_PRESENT |
1.5 |
Presence penalty: discourages repeating previously used tokens |
LIM_PENALTY_REPEAT |
1.0 |
Repetition penalty multiplier (1.0 = no penalty) |
LIM_SEED |
(auto) | Random seed for reproducibility (default is time-based) |
LIM_TEMP |
0.7 |
Sampling temperature (set to 0 for deterministic/greedy decoding) |
LIM_THINKING |
1 |
Set to 0 to suppress thinking blocks via a pre-filled stub for faster throughput. Not recommended for math or complex reasoning tasks, as it can cause incorrect answers by skipping intermediate steps. |
LIM_ESCAPE_CONTRACT |
0 |
Set to 1 to include the reserved-token escape contract in the system prompt. The escape mechanism itself is always active; this only controls whether the LLM sees the explicit rules. |
LIM_INLINE_LATEX |
1 |
Set to 0 to disable inline KaTeX rendering in the browser viewer. Block-level $$...$$ math still renders via katex.renderToString. Useful when $HOME, $PATH, and other env vars appear as bare text and get mangled by LaTeX processing. |
LIM_TOP_K |
20 |
Keep only the top_k most likely tokens before applying other samplers |
LIM_TOP_P |
0.8 |
Nucleus sampling: consider tokens with cumulative probability <= top_p |
LIM_CACHE_TYPE_K |
Q8_0 |
KV-cache key storage type (F16, Q4_0, Q5_0, Q5_1, Q8_0, Q8_1) |
LIM_CACHE_TYPE_V |
Q8_0 |
KV-cache value storage type (F16, Q4_0, Q5_0, Q5_1, Q8_0, Q8_1) |
LIM_CHATBOT_MODE |
0 |
Benchmarking mode: 0 = LIM normal (persistent KV-cache, same as llama-cli interactive), 1 = standard chatbot (re-decode full history each turn), 2 = cache-aware prefix match (emulates llama-server). Modes 1 and 2 force honest speed measurement. |
LIM_EXEC_TRUNCATION |
32768 |
Maximum bytes of exec_shell output before truncation |
LIM_MAX_AUTO_CONTINUE |
500 |
Maximum depth of automatic tool-call chaining |
LIM_TURN_TIMEOUT |
300 |
Maximum seconds per generation turn before auto-abort |
LIM_TASKSET |
(auto) | Format: "P_CORES:E_CORES" (e.g., "0-15:16-23"). Auto-detected on hybrid CPUs. Set to "::" to disable all pinning. |
LIM_TASKSET_CMD |
taskset -c |
Override the core-pinning command. On macOS (no taskset), install numactl via Homebrew and set to numactl --cpunodebind. If the command isn't on $PATH, pinning is silently skipped. |
Between user turns, LIM resets the sampler chain (penalties ring buffer and RNG seed). This means repetition penalties apply only to tokens generated during the current turn, not to stale tokens from previous responses, and not to the user's input. This differs from llama-cli, which feeds user input tokens into the sampler chain: in a chat interface, penalizing words the user explicitly used in their message would actively harm response quality. During auto-continue (tool-call chains, /continue), the sampler state is preserved so generation continues seamlessly.
The $LIM_AI_USER operates in a sandboxed environment:
- File access is limited to directories where the
$LIM_AI_USERgroup has write permission. Useaishareto grant access to new project directories. Files created by the LLM are owned by$LIM_AI_USER:$LIM_AI_USERwith group read/write permissions (viaumask 0002), so your personal user can access them directly since it is a member of the$LIM_AI_USERgroup. - Shell commands executed via the
exec_shelltool run as user$LIM_AI_USER. They inherit that user's PATH and environment. - Git integration: The sandbox repo is a separate git repository that is writable by
$LIM_AI_USERusing a dedicated AI GitHub account. This allows the LLM to commit, push, and manage version control autonomously without needing your personal credentials.
Set up an SSH key:
# As `$LIM_AI_USER`, generate a dedicated key
ssh-keygen -N ""
# Add the public key to your Git hosting provider
cat ~/.ssh/id_ed25519.pubSSH in as $LIM_AI_USER, then run your coder alias. You'll see the >>> prompt. Type your request and press Enter.
The LLM has access to six tools. You don't call them directly; just describe what you want and the LLM handles the rest:
read_files: Read text files, PDFs, or URLssearch_file: Search for text within a file, with optional line rangeedit_file: Replace exact text within a file (surgical editing)write_file: Write or overwrite a fileexec_shell: Run a shell command as$LIM_AI_USERweb_search: Search the web via SearXNG
Tools are invoked via XML-structured tags in the LLM's output. Results are fed back as continuation tokens into the KV-cache, avoiding the overhead of re-tokenizing the full conversation history.
The readline interface binds Ctrl+J to insert a literal newline into your input, while Enter (Return) submits the line. This lets you compose multi-line messages:
>>> Here's a function I need fixed:
(ctrl-j)
(ctrl-j)
def foo(x):
(ctrl-j)
return x + 1
(ctrl-j)
(ctrl-j)
Please fix the type hints. <-- Enter submits
The binding is restored after each input cycle so it doesn't leak into your shell session.
The prompt uses GNU readline in callback mode with select() polling instead of blocking I/O. This allows Ctrl+C interrupts to be handled immediately during user input, not just during generation. All standard Emacs-style bindings are available:
| Keys | Action |
|---|---|
| Ctrl+A / Ctrl+E | Beginning / end of line |
| Ctrl+B / Ctrl+F | Backward / forward character |
| Alt+B / Alt+F | Backward / forward word |
| Ctrl+K / Ctrl+U | Kill to end / start of line |
| Ctrl+W | Kill word backward |
| Ctrl+Y | Yank (paste) |
| Ctrl+_ | Undo |
| Tab | Completion (if configured) |
| Up / Down | History navigation |
| Ctrl+R | Reverse search history |
| Command | Effect |
|---|---|
/quit or /exit |
Auto-save the current state to $LIM_LOG_DIR/<N>.save, then exit the session |
/clear |
Auto-save the current state to $LIM_LOG_DIR/<N>-clear.save, then clear the KV-cache (reset to system prompt only). The auto-saved file lets you restore if you change your mind. Use /save <name> to create a permanent restore point before clearing. |
/undo |
Interactive undo: auto-saves first to $LIM_LOG_DIR/<N>-clear.save, then presents an Undo> prompt listing all checkpoints (most recent first). Use up/down arrows to navigate, Enter to confirm. Ctrl+C, Ctrl+D, /quit, or /exit cancel the undo and return to the user prompt without losing your session. Selecting a checkpoint restores the session to the end of the turn associated with that prompt. Falls back to clear+re-decode only if no checkpoint is available. Readline history is updated to reflect the restored session state. |
/continue |
Resume generation after an interruption. If interrupted mid-tool-call, resumes from the exact point of interruption |
/reset |
Reset terminal, loop detector, and web search. Useful for recovering from a corrupted terminal or disabled web search after an interrupt or connection failure |
/reincarnate |
Ask the LLM to compose a new prompt in ~/.config/lim/userprompt, then clear and restart with it |
/save |
Save the full session state to $LIM_LOG_DIR/<N>.save, overwriting any previous save for this session |
/save <path> |
Save the full session state to <path>.save. The path can be relative or absolute. If it already ends in .save, no extra extension is added. Use this to create named restore points at meaningful moments in your session. |
/load <path> |
Load a saved session from within the current session. Must be used immediately after /clear: fails if any conversation tokens have been added since the clear. Accepts the same path format as /save: .save is appended automatically if not already present. Uses the fast cache when available, falling back to full re-decode with checkpoint regeneration. |
/delete <path> |
Delete a save file and its associated fast restore cache entry (if any). Accepts the same path format as /save: .save is appended automatically if not already present. |
/help |
Display a summary of all available commands |
You can save a running session and restore it later with zero context loss:
Save: Type /save at the >>> prompt to save to $LIM_LOG_DIR/<N>.save (overwrites any previous save for this session). Use /save <path> to create named checkpoints: e.g., /save cats saves to cats.save, and /save /tmp/checkpoint saves to /tmp/checkpoint.save. If the path already ends in .save, no extra extension is added. The save file contains only the conversation token sequence, keeping it small and model-agnostic. Named saves also cache the full KV-cache for fast future restores.
Restore: Pass a save file as the last argument to coder. The .save extension is added automatically if not already present, matching /save behavior:
coder $LIM_LOG_DIR/5.save # explicit extension
coder $LIM_LOG_DIR/5 # .save appended automatically
coder cats # restores from cats.saveThis restores the session exactly as it was: the full conversation, KV-cache position, and generation state. The LLM continues generating from where it left off. Typing /clear after a restore resets to a fresh system prompt with the current date and working directory (but first auto-saves the restored state).
In-session load: You can also restore from within a running session using /load <path>. This must be used immediately after /clear; if any conversation tokens have been added since the clear, the command will fail. It uses the same path conventions as /save (.save appended automatically) and takes advantage of the fast cache when available. Typical workflow:
>>> /clear
[Context Cleared Successfully]
>>> /load cats
[Restoring session from cats.save... (75432 tokens, from cache)]
[Session restored: 75432 tokens loaded]
Partial restore via checkpoints: Save files record a checkpoint at the end of each conversation turn, storing your prompt text and the token position. On restore, if a fast-format cache is not available, LIM offers a choice of checkpoints before decoding. Use up/down arrow keys to navigate through your prompts (most recent first). Press Enter to confirm. If no checkpoint matches your input, all tokens are restored by default. Press Ctrl+C or Ctrl+D to cancel the restore and start a fresh session. Type /quit or /exit to exit LIM entirely. Restoring to a checkpoint replays tokens only up to the end of that turn -- as if you had just typed that prompt and received the response, and the session is ready for your next message. The available checkpoints accumulate across restore/save cycles: restoring from a save file carries over its checkpoints, and new turns add more.
Checkpoint restore: Add --checkpoints to skip the fast-format cache and trigger the checkpoint selection prompt. The flag can appear before or after the save file:
coder cats --checkpoints
coder --checkpoints catsFast restore cache: On first restore, tokens are decoded through the model to rebuild the KV-cache. During this decode, recurrent-state checkpoints are regenerated at each prompt boundary so that /undo works instantly for hybrid models (Qwen3.5/3.6) right after restore. The rebuilt cache is then automatically written to $LIM_CACHE_DIR/<hash> so all subsequent restores from the same save file are fast. Named saves (e.g., /save cats) also write the fast-format cache immediately for faster future restores. The name prefix in the cache filename is purely informational; LIM identifies cache files by their content hash, not their name. If you run /save cats followed by /save dogs with identical session content, only one cache file is written since both resolve to the same hash. Unnamed /save and auto-saves from /quit, /clear, and /reincarnate skip the fast cache to save disk space, relying on the automatic cache built on first restore. For fast cache restores, recurrent checkpoints build up naturally as conversation turns complete to support the instant /undo feature after the first new turn. The $LIM_CACHE_DIR/ directory is safe to delete at any time to reclaim space; it will be regenerated on the next restore.
Auto-save on clear and quit: Before clearing the context, LIM automatically saves the current state to $LIM_LOG_DIR/<N>-clear.save. Before exiting, it saves to $LIM_LOG_DIR/<N>.save. These use different filenames so neither clobbers the other: if you clear and then exit, both the pre-clear and post-clear states are preserved. To keep a permanent checkpoint at any point, use /save <name>.
Press Ctrl+C during generation to interrupt. The partial output is preserved in the KV-cache. Type /continue to resume seamlessly: the LLM doesn't even know it was interrupted. This works because the KV-cache still holds all generated tokens up to the interruption point.
By default (LIM_OUTPUT=2), LIM streams output to a browser via WebSocket. Start your LIM session first, then load:
http://<hostname>:8765/viewer.html
This ensures the server is already running when the browser connects, avoiding any need to reload the page.
The Python server (limServer.py) is auto-started by the C++ binary when browser output is enabled. It runs on efficiency cores (auto-detected, or all cores on non-hybrid CPUs). The server reads from a named FIFO at /tmp/lim.fifo and broadcasts to all connected WebSocket clients.
The LIM Workspace extension provides a convenient way to set up your workspace: it creates an integrated terminal and opens a browser viewer once the server is ready.
The extension does two things:
- Opens a browser pointing to
$LIM_HOST(or localhost) on the configured port, after waiting for the server to respond. - Creates a terminal at
$HOME. If$LIM_HOSTis set and remote, it SSHs into that host as user$LIM_AI_USER; otherwise it opens a local shell.
You then run your coder alias in that terminal as usual.
Install:
make vscode # builds and packages the extension
make install-vscode # installs the extension into VS CodeTo install everything (binary, config files, and VS Code extension) in one step:
make install-all # runs make install && make install-vscode
make uninstall-all # removes installed files and uninstalls the extensionThen in VS Code, click the LIM rocket icon in the status bar (or Ctrl+Shift+P -- LIM: Open Workspace). This opens a terminal panel and waits for the browser server. Run your coder alias in that terminal, and the viewer will connect automatically once the server starts.
To rebuild both the C++ binary and the extension together:
make allLIM supports benchmarking modes controlled by LIM_CHATBOT_MODE to compare its persistent KV-cache approach against standard chatbot and cache-aware decoding. Each mode writes a TPS log (log/<N>.tps) recording context position and tokens-per-second for every turn.
| Value | Mode | Description |
|---|---|---|
0 (default) |
LIM normal | KV-cache persists across turns. Each token is decoded once and never re-decoded. Same approach as llama-cli interactive mode. |
1 |
Standard chatbot | Clears cache each turn, reconstructs conversation text from stored tokens, re-tokenizes from scratch, and re-decodes through the model. Simulates a real chat API that receives the full conversation as text each request. TPS includes re-tokenize + re-decode (detokenization is excluded -- it's a LIM implementation detail, not part of chatbot behavior). |
2 |
Cache-aware prefix match | Emulates llama-server behavior: KV-cache stays in memory, but each turn re-tokenizes the full conversation text and compares against the cached prefix to find where to resume decoding. Illustrates that prefix matching overhead is negligible compared to decode cost. |
Chatbot modes (1 and 2) automatically enforce LIM_HONEST_SPEED=1. The TPS reported in logs includes the full re-decode overhead. This ensures the benchmark numbers reflect the true wall-clock cost of each approach.
Note: For fair comparisons, run benchmarks with an empty system prompt (remove
~/.config/lim/prompt) to inhibit tool calls. In mode 1, previous tool calls would be both re-decoded and re-executed on every turn, causing massive slowdowns plus unwanted side effects.
LIM can start SearXNG and Docling automatically when needed, but you must install them first:
cd ~
git clone https://github.com/searxng/searxng.git
cd searxng
pip install --no-build-isolation -e .Then edit searx/settings.yml: set a value for server.secret_key (required). The defaults for bind_address (127.0.0.1) and port (8888) are already correct for LIM. You may need to disable some search engines that block automated queries -- see the SearXNG docs for details. Override the start command with LIM_SEARXNG_CMD if installed elsewhere.
cd ~
python3 -m venv venv
source venv/bin/activate
pip3 install docling-serve
deactivateLIM expects the Docling binary at ~/venv/bin/docling-serve and starts it on port 5001. Override the start command with LIM_DOCLING_CMD if installed elsewhere.
The FIFO at /tmp/lim.fifo is created automatically when LIM starts. If it was removed manually, restart your LIM session. Ensure aiohttp is installed: pip3 install aiohttp.
LIM tries to connect to SearXNG on 127.0.0.1:8888. Make sure SearXNG is installed at ~/searxng/ and configured to listen on that port in settings.yaml. You can also run SearXNG externally and point LIM to it -- just ensure the server is already listening before your first web search.
Ensure Docling is installed. If it's not at ~/venv/bin/docling-serve, set LIM_DOCLING_CMD to the correct command, e.g.:
export LIM_DOCLING_CMD="/opt/docling/bin/docling-serve run --enable-ui"If you don't have an NVIDIA GPU, build for CPU only:
make GGML_CUDA=off- Start LIM first, then open the viewer URL. The server must be running before the browser connects.
- Check that nothing else is using port 8765 (or your custom
LIM_PORT). - If behind a firewall or SSH tunnel, set
LIM_VIEWER_URLto the correct address.
See Git Safe Directories in the User Setup section. Add [safe] directory = /home/$LIM_AI_USER to your personal ~/.gitconfig.
Input history is persisted to .lim_history in the current working directory and survives across sessions.
Each session writes a numbered log file in $LIM_LOG_DIR/<N>. With LIM_DEBUG=1, a companion token-level trace is written to $LIM_LOG_DIR/<N>.tokens showing every token fed into and generated by the model, with labels like FEED SYSTEM_PROMPT_INIT, FEED USER_INPUT, etc. A TPS log is also written to $LIM_LOG_DIR/<N>.tps recording context token count and tokens-per-second at each speed diagnostic update.
See LICENSE for details.
