Skip to content

wolfmib/johnalin

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

2 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

johnalin

Self-contained agentic CLI for local LLMs. REPL + one-shot CLI, with tool whitelisting, SQLite audit, and per-call artifact bundles. The production-grade discipline most local-LLM frameworks lack — in 1 ~MB pip install.

PyPI Python License: MIT


What you get

pip install johnalin
ollama pull qwen2.5:7b              # or any OAI-compatible local model
johnalin                            # interactive REPL
johnalin-cli --tools glob,bash -m "count .py files in this repo"   # one-shot

Two binaries, one package, zero servers to run separately. Talks to ollama (or any OpenAI-compatible local backend) directly, runs the tool loop in-process, writes a forensic audit bundle for every call.

Why this exists — four things most local-agent CLIs skip

1. Tool whitelisting per call (system-prompt 12K → 2K)

Most frameworks dump every registered tool into the system prompt on every turn. johnalin lets you specify exactly which tools the agent gets per call:

johnalin-cli --tools file_read,grep -m "find TODO comments under src/"
johnalin-cli --tools bash -m "show the last 3 git commits"

Smaller system prompt = lower latency, less "lazy" model behaviour, less cost on hosted backends.

2. Instrumented artifact bundles

Every call writes a forensic record under ~/.johnalin/artifacts/YYYYMMDD/iter_<utc>Z/<run_id>/:

metadata.json   — run_id, agent_tag, host, timings, context-fit verdict
request.json    — what we sent (message + tools + cwd + model)
response.json   — what we got (text + stopped reason + status)
trace.json      — every event the engine emitted (turn_start, usage,
                  tool_call (with args + result), turn_end, etc.)

Real metadata.json from the showcase below:

{
  "run_id": "cli_f4ab113608f5",
  "executor": "johnalin",
  "duration_ms": 3830,
  "context": {
    "peak_prompt_tokens": 498,
    "max_context": 16384,
    "utilization": 0.03,
    "fits": true,
    "risk": false
  }
}

3. SQLite history shared by REPL + CLI

Both johnalin and johnalin-cli log every turn to ~/.johnalin/history.db with the same schema. Grep across modes, query timings, replay turns:

SELECT run_id, model, stopped, turns, in_tokens, out_tokens, duration_ms, substr(input_text,1,55)
FROM turns ORDER BY ts DESC LIMIT 5;

4. Exit-code discipline (compose meta-agents reliably)

johnalin-cli returns distinct exit codes so wrappers can branch deterministically:

Exit Meaning
0 stop — model finished cleanly
1 tool_calls_unparseable — model emitted broken JSON args
2 repeat_loop — same tool call twice in a row, killed
3 max_turns — tool loop hit the cap without finishing
4 http/network error reaching ollama
5 bad CLI args
johnalin-cli --tools file_read,grep -m "$prompt" || handle_failure $?

Showcase (live output, captured 2026-05-07)

Three real johnalin-cli invocations against a local Qwen 27B running in ollama. Replicate with:

export OLLAMA_MODEL=qwen2.5:7b   # or whatever model you pulled
export JOHNALIN_HOME=/tmp/johnalin_showcase

Demo 1 — plain chat (no tools)

$ johnalin-cli --tools NONE -m "/no_think Reply with exactly: hello from johnalin v0.1.0"

The user wants me to reply with a specific string. I will output that string.

hello from johnalin v0.1.0
run_id=cli_0b5b0a23321e stopped=stop duration_ms=10461

Demo 2 — tool-use (glob counts files)

$ johnalin-cli --tools glob -m "Use glob with pattern '**/*.py' and root='johnalin'.
                                Reply with just the count." --max-turns 4

18
run_id=cli_f4ab113608f5 stopped=stop duration_ms=3830

The model emitted a single glob({"pattern":"**/*.py","root":"johnalin"}) call, got 18 file paths back, and replied with the integer. 2 turns, 3.8 seconds.

Demo 3 — tool-use (file_read summarises)

$ johnalin-cli --tools file_read -m "Read /tmp/test_readme.md.
                                     Reply with just the line count." --max-turns 3

5
run_id=cli_3b18c9f746da stopped=stop duration_ms=1831

What landed on disk

$ ls /tmp/johnalin_showcase/
artifacts/    history.db

$ find /tmp/johnalin_showcase/artifacts -name '*.json' | wc -l
12   # 4 files × 3 demos

$ sqlite3 /tmp/johnalin_showcase/history.db \
    "SELECT run_id, stopped, turns, in_tokens, duration_ms FROM turns"
cli_0b5b0a23321e | stop | 1 | 135 | 10461
cli_f4ab113608f5 | stop | 2 | 498 | 3830
cli_3b18c9f746da | stop | 2 | 438 | 1831

A real trace.json excerpt (Demo 2)

{
  "events": [
    { "type": "system_prompt_built", "systemPromptChars": 440, "toolCount": 1 },
    { "type": "turn_start", "turn": 0 },
    { "type": "usage", "turn": 0, "prompt_tokens": 348, "completion_tokens": 82 },
    { "type": "assistant_message", "turn": 0, "finish_reason": "tool_calls", "tool_call_count": 1 },
    { "type": "tool_call", "turn": 0, "name": "glob",
      "args": { "pattern": "**/*.py", "root": "johnalin" }, "ok": true,
      "result_preview": "johnalin/__init__.py\njohnalin/__main__.py\njohnalin/audit.py\n..." },
    { "type": "turn_end", "turn": 0, "stopped": "continued" },
    { "type": "turn_start", "turn": 1 },
    { "type": "assistant_message", "turn": 1, "finish_reason": "stop" },
    { "type": "turn_end", "turn": 1, "stopped": "stop" }
  ]
}

Every tool call captured with name, args, ok, result_preview, result_chars — the exact debug surface most local-LLM CLIs don't give you.


Install

pip install johnalin

# Backend: any OpenAI-compatible local LLM
# Easiest is ollama:
curl -fsSL https://ollama.com/install.sh | sh
ollama pull qwen2.5:7b              # ~4.7 GB, runs on 8 GB VRAM
ollama serve &                       # in another terminal

Then:

johnalin                                       # REPL
johnalin-cli --tools NONE -m "say hi"         # one-shot smoke test

Configuration

All paths configurable. Zero hardcoded paths anywhere in the package.

Env var Default Purpose
OLLAMA_BASE_URL http://127.0.0.1:11434/v1 OpenAI-compat endpoint
OLLAMA_MODEL qwen2.5:7b Model id sent to the backend
JOHNALIN_NUM_CTX 16384 Context window passed as options.num_ctx
JOHNALIN_HOME ~/.johnalin Base dir for db + artifacts
JOHNALIN_DB $JOHNALIN_HOME/history.db SQLite history
JOHNALIN_ARTIFACTS $JOHNALIN_HOME/artifacts Per-call bundle root
JOHNALIN_MAX_TURNS 10 Tool-loop cap
JOHNALIN_TIMEOUT 900 HTTP timeout to backend (seconds)
JOHNALIN_AGENT_TAG user Stamped into every metadata.json

CLI flags (--model, --num-ctx, --base-url, --max-turns, --cwd) override env per call.

Built-in tools (v0.1)

Tool Schema Purpose
bash command, [timeout] Run a shell command; returns [exit N]\n<output> capped at 16 KB
file_read path, [offset], [limit] Read UTF-8 file with line numbers, 64 KB cap
file_write path, content Overwrite text file (creates parent dirs)
glob pattern, [root] Find files by glob (supports **), max 200 results
grep pattern, [path], [glob] Search file contents (uses rg if available)

Custom tools register at runtime — see examples/custom_tool.py.

REPL (johnalin)

  • Multi-line input: paste freely, Esc-Enter (or Alt-Enter) submits
  • Slash commands: /tools, /notools, /stats, /model, /help, /exit
  • Per-turn debug panel: input, assistant reply (markdown rendered), tool calls with args + result preview, summary table with token counts + context-fit verdict + duration + artifact path
  • Every prompt runs against a fresh context (no chat-history accumulation in memory; SQLite + scrollback are your durable history)
  • Auto-detects upstream health on startup; gives you a clear "ollama not running, start it like this" message if down

CLI (johnalin-cli)

johnalin-cli --tools <list>|NONE  --message <text>
             [--max-turns N]      [--cwd DIR]
             [--model M]          [--base-url URL]   [--num-ctx N]
             [--no-audit]         [--quiet]

stdout: model reply text only. stderr: run_id=... stopped=... duration_ms=... (and warnings).

Development

git clone https://github.com/wolfmib/johnalin.git
cd johnalin
python -m venv .venv && source .venv/bin/activate
pip install -e .[dev]
pytest tests/ -v        # 26 tests
ruff check johnalin tests

License

MIT — see LICENSE.

Acknowledgements

The architecture (tool whitelisting + audit bundling + exit-code discipline) was iterated on over months in a private fork before being lifted into this generic, self-contained release. If you've built local-LLM agents and hit the "system-prompt bloat" problem, this is the production-grade discipline that fixes it.

Comparable projects: OpenWebUI, Continue, LangChain, AutoGPT — all powerful, none combine all four differentiators above in one ~600-line agentic CLI you can pip install in 5 seconds.

About

Self-contained agentic CLI for local LLMs — REPL + one-shot, with tool whitelisting, SQLite audit, and per-call artifact bundles.

Topics

Resources

License

Stars

0 stars

Watchers

0 watching

Forks

Packages

 
 
 

Contributors

Languages