algocline
Research-grade reasoning for your LLM — as Pure Lua you can read, edit, and ship.
UCB1 exploration, self-reflection, multi-agent debate, chain-of-verification — each strategy is a single Lua file. Install with cargo install, customize anything, no framework lock-in.
What it does
LLMs are single-shot by default. Ask a question, get one answer, hope it's right. Research papers describe techniques that do better — iterating, scoring, selecting — but using them today means Python, framework lock-in, and API key management.
algocline makes these techniques immediately usable. Each algorithm is a Pure Lua file (100-350 lines) that runs inside your existing MCP host. No infrastructure. No setup beyond alc init.
Human ──→ LLM (MCP host) ──→ algocline ──→ Algorithm (Lua) ──→ alc.llm() ──→ back to LLM
↑ │
└──────────────┘
(loop until done)
Use existing algorithms with one call. Build your own by writing Lua. Share them via Git.
When to use
| What you want to do | What happens | Strategy |
|---|---|---|
| Catch what your code review missed | Draft → self-critique → revise. Found that std::fs::write is non-atomic (crash = data loss) after the first draft said "no issues" |
reflect |
| Make design decisions structurally, not by gut | Advocate, critic, and pragmatist debate. Resolved "do we need log rotation?" → "no, but add a display limit" in 4 rounds | panel |
| Fact-check LLM claims before trusting them | Auto-generates verification questions → checks each one. Escalated a path traversal from medium to high severity after confirming PathBuf::join() does zero validation |
cove |
| Break down a complex problem step by step | Builds reasoning chain incrementally. Structured Vec vs HashMap trade-offs into cache locality → memory overhead → API ergonomics | cot |
| Pick the best option from multiple candidates | Scores each candidate on your criteria, pairwise comparison | rank |
| Get a reliable answer, not a lucky one | Generates N answers → majority vote picks the most consistent one | sc |
Why algocline
| Approach | Limitation |
|---|---|
| Prompt engineering | Single-shot. No iteration, no scoring, no selection |
| DSPy / LangGraph | Python infra. Pip install. API keys. Framework lock-in |
| algocline | Zero infra. Pure Lua. Runs inside your existing MCP host. Use research-grade algorithms today, or write your own |
Quick start
1. Install
2. Initialize packages
This downloads the bundled package collection (UCB1, chain-of-thought, self-consistency, etc.) into ~/.algocline/packages/. Run alc init --force to overwrite existing packages.
It also distributes alc.d.lua — LuaCats type definitions for all alc.* StdLib functions — to ~/.algocline/types/alc.d.lua. This enables editor completion in any editor backed by lua-language-server. If .luarc.json is not present in the current directory, a setup tip is printed.
Note: If you skip this step, packages are auto-installed on first
alc_advicecall via MCP. But runningalc initupfront is recommended for faster first use and offline availability.
3. Add to your MCP config
By default, alc runs as an MCP stdio server — it is designed to be launched by an MCP client
such as Claude Code, not invoked directly from a shell. For one-shot maintenance tasks, use the
alc init and alc update subcommands. For ad-hoc shell access to algocline MCP tools, set up
an agent-block harness that starts alc as a child process and calls the MCP server directly;
running plain alc from a terminal will start the server in the foreground waiting for an MCP
client connection. Run alc --help for a summary of available subcommands.
Add algocline as an MCP server in your host's configuration (e.g. Claude Code's ~/.claude.json, Cursor's MCP settings, etc.):
After adding the config, restart your MCP host session so it picks up the new server.
Environment variables
| Variable | Description | Default |
|---|---|---|
ALC_LOG_DIR |
Directory for session transcript logs | ~/.algocline/logs |
ALC_LOG_LEVEL |
full (enable logging) or off (disable) |
full |
ALC_PACKAGES_PATH |
Additional package search paths (colon-separated). Takes priority over ~/.algocline/packages/ |
(none) |
ALC_PROJECT_ROOT |
Project root directory for project-local package resolution. When omitted, auto-detected by walking up from cwd to find alc.toml |
(auto-detect) |
ALC_POOL_STATE_DIR |
Directory for pool registry state (registry.json). Overrides the default ~/.algocline/state/pool/ |
~/.algocline/state/pool |
ALC_POOL_IDLE_TIMEOUT |
Idle timeout in seconds for pool worker subprocesses. Set to 0 to disable. Only relevant when using host_mode: true |
1800 |
Example: writing logs to a custom directory:
4. Use
Call algocline tools from your MCP host. The host LLM calls these tools on your behalf:
One-liner with an installed package:
alc_advice({ strategy: "ucb", task: "Design a rate limiter for a REST API" })
Or write your own Lua:
-- alc_run({ code: "..." })
local draft = alc.
local critique = alc.
local final = alc.
return
Architecture
Three-Layer StdLib
Layer 0: Runtime Primitives (Rust → alc.*)
│ alc.llm(prompt, opts?) — Host LLM call via MCP Sampling
│ alc.llm_batch(items) — parallel LLM calls (single round-trip)
│ alc.fork(strategies, ctx, o?) — parallel multi-VM strategy execution
│ alc.json_encode/json_decode — serde_json bridge
│ alc.log(level, msg) — tracing bridge
│ alc.state.get/set/keys/delete — persistent key-value store
│ alc.match_enum(text, cs, o?) — fuzzy enum match from LLM output
│ alc.match_bool(text) — yes/no normalizer for LLM output
│ alc.budget_remaining() — remaining budget (calls/time)
│ alc.progress(step, total, m?) — structured progress reporting
│
Layer 1: Prelude Combinators (Lua → alc.*)
│ alc.cache(prompt, opts?) — memoized LLM call (session-scoped)
│ alc.parallel(items, fn, o?) — batch-parallel LLM over array
│ alc.map(items, fn) — transform each element
│ alc.reduce(items, fn, init) — fold to single value
│ alc.vote(answers) — majority aggregation
│ alc.filter(items, fn) — conditional selection
│ alc.json_extract(raw) — extract JSON from LLM output
│ alc.parse_number(text, pat?) — extract number from LLM output
│ alc.state.update(key, fn) — read-modify-write for state
│ alc.llm_safe(prompt, opts, d) — non-throwing LLM wrapper
│ alc.llm_json(prompt, opts?) — LLM call with JSON parse + retry
│ alc.fingerprint(str) — normalize + DJB2 hash (dedup)
│ alc.tuning(defaults, ctx) — config merge with deep merge
│ alc.budget_check() — boolean budget guard
│ alc.pipe(strategies, ctx) — sequential strategy pipeline
│ alc.eval(scenario, strategy) — evalframe facade (eval + Card)
│
Layer 2: Bundled Packages (require() from ~/.algocline/packages/)
cot — chain-of-thought [reasoning]
maieutic — maieutic prompting [reasoning]
reflect — self-reflection [reasoning]
calibrate — confidence calibration [reasoning]
sc — self-consistency (majority vote) [selection]
rank — pairwise ranking [selection]
triad — triad comparison [selection]
ucb — UCB1 hypothesis exploration [selection]
sot — skeleton-of-thought [generation]
decompose — task decomposition [generation]
distill — knowledge distillation [extraction]
cod — chain-of-density [extraction]
cove — chain-of-verification [validation]
factscore — factual precision scoring [validation]
panel — multi-perspective deliberation [synthesis]
Layer 0/1 are always available. Layer 2 packages are installed via alc init or alc_pkg_install from algocline-bundled-packages and loaded via require().
Crate structure
algocline (bin: alc)
├── algocline-core — Domain types, EngineApi trait (transport-independent API surface)
├── algocline-engine — Lua VM executor, session registry, bridge
└── algocline-mcp — MCP tool handlers (alc_run, alc_advice, etc.)
Execution model
Each alc_run / alc_advice call spawns a dedicated Lua VM (OS thread + mlua instance). Concurrent sessions are fully isolated — each session's alc, ctx, and package.loaded live in their own VM, so parallel executions cannot interfere with each other.
alc.llm() is a cooperative yield. When Lua calls it, the VM pauses and returns the prompt to the MCP host. The host processes the prompt with its own LLM, then calls alc_continue with the response to resume execution.
alc_run(code)
→ Spawn dedicated VM → Lua executes → alc.llm("prompt") → VM pauses
→ returns { status: "needs_response", prompt: "...", session_id: "..." }
alc_continue({ session_id, response })
→ Lua resumes → ... → alc.llm("next prompt") → VM pauses again
→ ...repeat until Lua returns a final value → VM cleaned up
MCP Tools
| Tool | Description |
|---|---|
alc_run |
Execute Lua code with optional JSON context. Pass host_mode: true to run the session in a long-lived pool worker subprocess that survives MCP server restarts |
alc_continue |
Resume a paused execution with the host LLM's response. Automatically routes to a pool worker when the session was started with host_mode: true |
alc_advice |
Apply an installed package by name |
alc_pkg_link |
Link a local directory as a project-local package via symlink. Records path in alc.lock |
alc_pkg_unlink |
Remove a symlink created by alc_pkg_link (rejects real directories) |
alc_pkg_list |
List installed packages with metadata. Pass project_root to include project-local packages |
alc_pkg_install |
Install a package or collection from Git URL or local path. Pass force: true to overwrite an already-installed package. Response includes types_path (absolute path to alc.d.lua) |
alc_pkg_remove |
Remove an installed package from alc.toml + alc.lock. Pass project_root to target project scope |
alc_init |
Initialize a project — creates alc.toml in the project root if absent |
alc_update |
Update packages declared in alc.toml by re-installing from their recorded sources |
alc_migrate |
Migrate a legacy alc.lock to the new alc.toml + alc.lock schema |
alc_eval |
Evaluate a strategy against a scenario (cases + graders) |
alc_eval_history |
List past eval results, filter by strategy |
alc_eval_detail |
View a specific eval result in full detail |
alc_eval_compare |
Compare two eval results with Welch's t-test |
alc_note |
Add a note to a completed session's log |
alc_log_view |
View session logs (list or detail) |
alc_stats |
Aggregate usage stats across sessions (per-strategy) |
alc_status |
Query active session status, progress, and metrics |
alc_info |
Show server configuration and diagnostics |
alc_hub_search |
Search packages across remote Hub indices + local state |
alc_hub_info |
Show detailed info for a single package (metadata, Cards, aliases, stats) |
alc_hub_reindex |
Rebuild Hub index from locally installed packages |
alc_hub_dist |
Generate and distribute documentation for a package hub (hub, context7, devin, lint, lint_only, luacats, narrative, llms projections) |
alc_hub_gendoc |
Generate documentation for a single package (hub, context7, devin, lint, lint_only, luacats, narrative, llms projections) |
alc_pkg_scaffold |
Generate a minimal package skeleton with M.meta / M.run template and pre-filled alc_shapes_compat range |
alc_scenario_list |
List installed eval scenarios |
alc_scenario_show |
Show an installed scenario's content |
alc_scenario_install |
Install scenarios from Git URL or local path |
alc_pool_ensure |
Ensure a pool worker subprocess is running for a session; spawns one if absent. Returns {sid, sock, pid, status} |
alc_pool_status |
List active pool sessions with PID, socket path, and version. Also runs GC to evict stale entries and persists the updated registry |
alc_pool_stop |
Send SIGTERM to a named session's pool worker and remove its registry entry |
MCP Resources
algocline exposes structured data via MCP Resources — a standard capability that lets MCP clients read named files and catalog data without calling tools.
Resource URIs
| URI | Type | Description |
|---|---|---|
alc://types/alc.d.lua |
Fixed | LuaCats type definitions for alc.* StdLib (editor completion) |
alc://types/alc_shapes.d.lua |
Fixed | LuaCats definitions for alc_shapes shape library |
alc://hub/index |
Fixed | Aggregated hub package catalog (application/json) — merges all cached hub_index.json files across registered sources |
alc://packages/{name}/init.lua |
Template | Package init.lua source |
alc://packages/{name}/meta |
Template | Package metadata JSON |
alc://cards/{card_id} |
Template | Eval card JSON |
alc://scenarios/{name} |
Template | Scenario Lua source |
alc://eval/{result_id} |
Template | Eval result JSON |
alc://logs/{session_id} |
Template | Session transcript log |
Claude Code @-mention
When algocline is configured as an MCP server, Claude Code exposes all resources via @alc: mention syntax. Type @alc: in the input field to browse or tab-complete resource URIs:
@alc:alc://hub/index # read the full package catalog
@alc:alc://packages/reflect/init.lua # read the reflect package source
@alc:alc://types/alc.d.lua # read StdLib type definitions
Tab-completion for template arguments is supported (completion/complete). Typing @alc:alc://packages/ and pressing <TAB> returns installed package names filtered by prefix.
Caveat: Claude Code subagents launched with
subagent_typemay not inherit the parent session's MCP resource access. Read resources from the main agent context, not from within a delegated subagent.
alc://hub/index — aggregated package catalog
Reading alc://hub/index returns a JSON object with the union of all registered hub sources:
Individual source read failures are surfaced in "warnings" rather than failing the whole request (best-effort aggregate). On a clean install with no cached sources the response is {"schema_version":"hub_index/v0","packages":[]}.
Host integration patterns
algocline's alc.llm() is a cooperative yield — it pauses the Lua VM and returns a prompt to the host. How the host handles this determines performance and quality.
Pattern 1: Manual loop (baseline)
The host LLM reads each prompt, generates a response, and calls alc_continue. Simple but requires one round-trip per alc.llm() call.
Host LLM → alc_advice → needs_response → Host reads prompt → Host generates response → alc_continue → repeat
Best for: Interactive exploration where you want to inspect each step.
Pattern 2: Autonomous agent delegation (recommended)
Delegate the entire strategy execution to a single agent that has MCP tool access. The agent calls alc_advice, handles every needs_response internally, and returns only the final result.
Host LLM → Agent(MCP-capable) → [alc_advice → needs_response → self-respond → alc_continue → ...] → final result
Best for: Production use. Zero host intervention. Fastest execution.
Example (Claude Code):
Agent(general-purpose) with prompt:
1. Call alc_advice(strategy="explore", task="...")
2. For each needs_response: generate a response following the system/prompt instructions
3. Call alc_continue with your response
4. Repeat until status="completed"
5. Return the final result
Known limitation: MCP tool permissions in subagents
Claude Code subagents may not inherit MCP tool permissions from the parent session's
settings.json. Ifalc_run/alc_continuecalls fail with permission errors inside a subagent, add the following to your~/.claude/settings.jsonunderpermissions.allow:"mcp__alc__*"If the issue persists, run the
alc_run/alc_continueloop directly from the main agent instead of delegating to a subagent.
Pattern 3: MCP Sampling (future)
When the MCP host supports server-initiated sampling, alc.llm() will resolve automatically without pausing. No agent delegation needed — the host responds inline.
Performance comparison
Benchmarked on the same task (UCB1 explore, 11 LLM calls):
| Pattern | Time | Host interventions | Notes |
|---|---|---|---|
| Manual (Opus) | 152s | 9 | High quality, manual effort |
| SubAgent relay (Haiku) | 224s | 9 | Agent startup overhead per call |
| SubAgent relay (Opus) | 442s | 9 | Same overhead, better quality |
| Autonomous agent | 69s | 0 | Single agent, full MCP access, no relay overhead |
The autonomous agent pattern eliminates relay overhead entirely. The agent handles the full alc_advice → alc_continue loop in-process, resulting in ~2x faster execution than even manual operation.
Strategy selection guide
| Strategy | Structure | Best for |
|---|---|---|
explore / ucb |
Generate hypotheses → UCB1 score → refine best | Open-ended questions, design decisions |
triad |
3-role debate (proponent/opponent/judge) | Comparative analysis, pro/con evaluation |
panel |
Multi-perspective deliberation + moderator | Complex problems needing diverse viewpoints |
verify / cove |
Draft → verify → revise | Factual accuracy, reducing hallucination |
reflect |
Generate → critique → revise loop | Iterative improvement |
ensemble / sc |
Multiple answers → majority vote | When consistency matters more than novelty |
rank |
Pairwise tournament ranking | Selecting best among candidates |
factscore |
Atomic claim decomposition + verification | Fact-checking, claim validation |
cod |
Iterative information densification | Summarization, compression |
Evaluating strategies
algocline includes a built-in evaluation framework powered by evalframe. Define scenarios with test cases and graders, then run them against any strategy to measure quality.
Define a scenario
A scenario is a Lua table with bindings (graders) and cases (input/expected pairs):
-- scenario.lua
local ef = require
return
Run an eval
alc_eval({ scenario: "...", strategy: "cove" })
Or point to a file:
alc_eval({ scenario_file: "/path/to/scenario.lua", strategy: "cove" })
The strategy is automatically wired as the provider — no boilerplate needed. Results include per-case scores, pass/fail status, and aggregate metrics.
Track and compare results
Eval results are persisted to ~/.algocline/evals/ automatically.
alc_eval_history({ strategy: "cove", limit: 10 }) # List past results
alc_eval_detail({ eval_id: "cove_1710672000" }) # Full result detail
alc_eval_compare({ eval_id_a: "...", eval_id_b: "..." }) # Welch's t-test
alc_eval_compare performs a Welch's t-test on the score distributions, reporting whether the difference between two runs is statistically significant.
Writing strategies
A strategy is a Lua file with an init.lua entry point:
-- my-strategy/init.lua
local M =
M. =
return M
Install it:
alc_pkg_install({ url: "github.com/you/my-strategy" })
Use it:
alc_advice({ strategy: "my-strategy", task: "..." })
Package management
Bundled packages
Bundled packages are maintained in algocline-bundled-packages. Install them via CLI:
If you call alc_advice with a package that isn't installed, algocline automatically downloads the bundled collection from GitHub. But alc init upfront is recommended.
To install or update via MCP:
alc_pkg_install({ url: "github.com/ynishi/algocline-bundled-packages" })
alc_pkg_install({ url: "github.com/ynishi/algocline-bundled-packages", force: true }) # overwrite existing
For local development (installs from a local checkout, supports uncommitted changes):
alc_pkg_install({ url: "/path/to/algocline-bundled-packages" })
Installing third-party packages
alc_pkg_install({ url: "github.com/user/my-strategy" })
alc_pkg_install automatically detects the repository layout:
| Layout | Detection | Behavior |
|---|---|---|
| Single package | init.lua at repo root |
Installed as one package |
| Collection | Subdirs containing init.lua |
Each subdir installed as a separate package |
Supported URL formats:
| Format | Example |
|---|---|
| GitHub shorthand | github.com/user/my-strategy |
| HTTPS | https://github.com/user/my-strategy.git |
| SSH | git@github.com:user/my-strategy.git |
| Local path (file://) | file:///path/to/my-strategy |
| Local path (absolute) | /path/to/my-strategy |
Optional parameters:
alc_pkg_install({ url: "/path/to/repo", name: "custom-name" }) # name override (single package mode only)
alc_pkg_install({ url: "/path/to/repo", force: true }) # overwrite if already installed
Managing packages
alc_pkg_list() # List installed packages with metadata
alc_pkg_remove({ name: "my-strategy" }) # Remove a package
Packages live in ~/.algocline/packages/. Each package is a directory with an init.lua.
Project-local packages
Project-local packages are managed via two files at the project root:
alc.toml— Package declarations (source of truth). Created byalc_initor automatically on firstalc_pkg_install.alc.lock— Resolved lockfile written by install/link operations.
Initialize a project:
alc_init({ project_root: "/path/to/project" })
Link a local directory as a project-scoped package via symlink (no copy):
alc_pkg_link({ path: "/path/to/my-strategy" })
This creates a symlink in ~/.algocline/packages/ and records the entry in alc.toml + alc.lock:
# alc.toml
[]
= "path"
= "/path/to/my-strategy"
Remove the symlink:
alc_pkg_unlink({ name: "my-strategy" })
Project root is auto-detected by walking up the directory tree to find alc.toml. You can also pass project_root explicitly.
Resolution order (highest priority first):
alc.lockpathentries — symlinked local directoriesALC_PACKAGES_PATH(environment)~/.algocline/packages/(global default)
Migrate an existing project using the old alc.lock schema:
alc_migrate({ project_root: "/path/to/project" })
Use project_root to activate project scope in other tools:
alc_pkg_list({ project_root: "/path/to/project" }) # Lists both project and global packages
alc_pkg_remove({ name: "my-strategy", project_root: "/path/to/project" }) # Removes from alc.toml + alc.lock
alc_update({ project_root: "/path/to/project" }) # Re-installs all alc.toml packages
Strategy development
Strategies are Pure Lua files. The host LLM can read, write, and execute them via alc_run — that's enough to iterate.
For debugging and testing, there are dedicated tools that run on the same mlua VM:
- mlua-probe — Lua debugger (MCP server). Breakpoints, stepping, variable inspection, expression evaluation
- mlua-lspec — BDD test framework (
describe/it/expect). Structured test results for CI/LLM consumption
Contributing
Bug reports and feature requests are welcome — please open an issue.
Pull requests are also appreciated. For larger changes, consider opening an issue first to discuss the approach.
Share your strategies
Writing a strategy package is straightforward: create init.lua, define M.meta and M.run(ctx), and you're done. If you build something useful, publish it as a Git repo and others can install it with:
alc_pkg_install({ url: "github.com/you/your-strategy" })
See Writing strategies and Package management for details.
License
MIT OR Apache-2.0