An interactive coding agent for the Hemlock programming language. Written in Hemlock, of course.
Hembot talks to a local llama-server running a Hemlock-tuned model (default: Hemlock-Apothecary-7B), extracts Hemlock code from its responses, runs it in a sandbox, and can optionally feed errors back so the model fixes its own bugs.
Use it in your terminal, over an HTTP API, or in a cute little web chat:
- Install Hemlock 2.0+ and make sure
hemlockis on yourPATH. - Install
llama-serverfrom llama.cpp. - Grab a GGUF of a Hemlock-tuned model, e.g.
Hemlock-Apothecary-7Bquantized to Q8_0. - Launch the server in another terminal:
llama-server -m Hemlock-Apothecary-7B-Q8_0.gguf --port 8199 --ctx-size 8192 -ngl -1
- Run Hembot (interpreted):
Or build a self-contained binary and run that:
hemlock src/hembot.hml
make build ./hembot
╔══════════════════════════════════════╗
║ Hembot — Hemlock Coding Agent ║
╚══════════════════════════════════════╝
you> write a program that prints the first 10 fibonacci numbers
Hembot: ```hemlock
fn main() {
let a = 0; let b = 1;
for (let i = 0; i < 10; i++) {
print(a);
let t = a + b; a = b; b = t;
}
}
main();
── sandbox: ✓ ran cleanly 0 1 1 2 3 5 8 13 21 34 ──
## Flags
| Flag | Default | Description |
|------|---------|-------------|
| `--url <url>` | `http://127.0.0.1:8199/v1/chat/completions` | OpenAI-compatible endpoint |
| `--model <name>` | `local` | Model name sent to the server |
| `--system <path>` | `system_prompt.txt` | System prompt file |
| `--no-exec` | off | Don't execute code blocks, just show them |
| `--retry` | off | Auto-retry up to 3 times when extracted code fails (hybrid strategy: feedback first, then cold resamples) |
| `--no-stream` | off | Disable token streaming; wait for the full response before printing |
| `--no-spinner` | off | Silence the "thinking" spinner and the timing line (clean scripted output) |
| `--serve` | off | Run an HTTP API instead of the interactive REPL (see below) |
| `--port <n>` | `8080` | Port to bind when serving |
| `--host <addr>` | `127.0.0.1` | Address to bind when serving |
## HTTP API
Run hembot as a local HTTP service instead of the REPL — handy for editors,
scripts, or other tools:
```bash
hemlock src/hembot.hml --serve --port 8080
# or the compiled binary: ./hembot --serve --port 8080
It points at the same --url llama-server as the REPL. Each request is
stateless (a fresh [system, user] conversation).
Open http://localhost:8080/ in a browser for a little 🌿 kawaii chat UI
(served from web/index.html; pass --no-ui to disable, or --ui <path> to
point elsewhere).
GET /health → {"status":"ok","model":"local"}
POST /chat — generate, extract code, and (optionally) run it:
curl -s -X POST http://127.0.0.1:8080/chat \
-d '{"prompt": "write a hemlock program that prints 7"}'{
"response": "```hemlock\nprint(7);\n```",
"code": "print(7);",
"sandbox": { "exit_code": 0, "output": "7\n" }
}execute(optional, defaulttrue) — set tofalseto skip the sandbox;sandboxis thennull.codeisnullwhen the response contains no fenced Hemlock.- Bad/empty
prompt→400; an upstream model failure →502.
Point hembot at a whole repo and give it tasks that span files:
hemlock src/hembot.hml --project path/to/repo
# task> add a heartbeat ping to the serverThe agent sees a repo map (every file's path · exports · imports) and pulls
file bodies in on demand. It drives the repo through a small tool protocol —
@read, @grep, @write, @edit (search/replace), @delete, @run,
@done — and edits land on the git working tree, so every change is a
reviewable, revertible diff.
Flags: --whole-file edits by rewriting entire files instead of search/replace
(slower / more tokens, but more reliable for weaker models); --max-steps <n>
bounds the agent loop (default 12). Overwriting an existing file requires the
agent to @read it first.
At the you> prompt:
/reset— clear conversation (keeps system prompt)/save <file>— save conversation as JSON/load <file>— resume a saved conversation/help— list the above
.
├── src/
│ ├── hembot.hml # Agent entry point (main loop, I/O, chat)
│ ├── extract.hml # Pure helpers for parsing LLM responses
│ └── config.hml # CLI argument parsing and defaults
├── tests/
│ ├── test_extract.hml # 11 unit tests for extraction
│ └── test_config.hml # 7 unit tests for config
├── .github/workflows/
│ └── test.yml # CI: build Hemlock, run the unit tests
├── system_prompt.txt # The winning hembench prompt
├── package.json # hpm metadata
└── README.md
make build # compile hembot binary via hemlockc
make test # run unit tests under the interpreter (no LLM needed)
make run # run the agent interpreted
make install # install to /usr/local/bin (honours PREFIX + DESTDIR)
make clean # remove build artifacts
# Also wired up as hpm scripts, but note there's an upstream hpm bug
# that throws at exit — the command still runs:
hpm run build
hpm testCI builds Hemlock from source on each push/PR and runs the full test suite. Tagged pushes (v*) trigger a release workflow that compiles hembot with hemlockc and attaches a Linux x86_64 tarball to the GitHub release.
The system_prompt.txt shipped here won a prompt sweep on Hemlock-Apothecary-7B (Q8_0) using hembench. It beat the benchmark's baseline prompt by ~13 points overall and pushed L4 (systems programming) from 57% to 86% pass rate.
Key ingredients:
- Hembot persona (aligns with how the base model was fine-tuned)
- Mention of interpreter availability so the model "mentally traces" before writing code
- Seven common-pitfall reminders (semicolons,
print()single-arg,/float,ptr_deref_*, etc.)
MIT
