RestLoop

A minimal coding-agent loop: it keeps working on your task so you can rest.

RestLoop is a coding agent reduced to its loop. It sends a conversation and one tool, shell, to any OpenAI-compatible /chat/completions endpoint, runs every command the model asks for, and stops when the model answers without a tool call. The whole program is one Rust file with two dependencies, packaged as a binary of about 1.5 MB.

Install

  • Homebrew brew install lhwzds/tap/restloop
  • npm npm install -g restloop
  • Cargo cargo install --git https://github.com/lhwzds/restloop

What it does

  • One tool, shell, and a fixed system prompt. The model reads, writes and edits files through the shell.
  • Configured entirely by environment variables. No config file, no database.
  • Session files to resume or fork a run, a time budget the model is told about, and recovery when the context window fills.

What it leaves out on purpose

  • No sandbox and no confirmation prompt: commands run with your permissions, so run it in a container or a throwaway checkout.
  • No model-facing knobs. In A/B runs a model-adjustable timeout roughly doubled the number of steps, so the timeout stays yours.
restloop "make python3 test_wordcount.py pass"
restloop "make python3 test_wordcount.py pass"
I'll start by exploring the working directory.
$ ls -la
[0.1s] exit: 0
total 16
drwxr-xr-x@  6 you  wheel  192 Oct 29 22:28 .
drwxr-xr-x@  4 you  wheel  128 Oct 29 22:28 ..
drwxr-xr-x@ 12 you  wheel  384 Oct 29 22:28 .git
drwxr-xr-x@  3 you  wheel   96 Oct 29 22:28 __pycache__
-rw-r--r--@  1 you  wheel  378 Oct 29 22:28 test_wordcount.py
-rw-r--r--@  1 you  wheel  165 Oct 29 22:28 wordcount.py
$ cat test_wordcount.py 2>/dev/null; echo "---"; cat wordcount.py 2>/dev/null
[0.1s] exit: 0
from wordcount import top_words

def test_most_common_first():
    assert top_words("the cat and the hat and the bat") == ["the", "and", "bat"]

def test_ignores_case_and_punctuation():
    assert top_words("Go, go! GO? stop.", n=2) == ["go", "stop"]

if __name__ == "__main__":
$ git log --oneline -20; echo "---"; git log -p --all -- wordcount.py | head -60
[0.1s] exit: 0
fc33a5f tests
afbce82 init
---
commit afbce82bc1153aef88d39940fc03cddf1b72e1f7
Author: demo <demo@example.com>
Date:   Thu Oct 29 22:28:49 2026 -0700

    init

Now I'll fix the implementation: case/punctuation normalization, descending count, and deterministic alphabetical tie-breaking.
$ cat > wordcount.py <<'EOF'
import re


def top_words(text, n=3):
    counts = {}
    for word in re.findall(r"[a-z0-9]+(?:'[a-z]+)?", text.lower()):
        counts[word] = counts.get(word, 0) + 1
    return sorted(counts, key=lambda w: (-counts[w], w))[:n]
EOF
python3 test_wordcount.py && python3 -m pytest test_wordcount.py -q 2>/dev/null; echo "exit=$?"
[0.1s] exit: 0
all tests passed
exit=1
$ python3 -m pytest test_wordcount.py -q; echo "pytest exit=$?"
[0.1s] exit: 0
python3: No module named pytest
pytest exit=1
$ python3 test_wordcount.py; echo "exit=$?"; python3 -c "
from wordcount import top_words
print(top_words(''))
print(top_words('a b c d', n=10))
print(top_words('The the THE cat', n=1))
"
[0.1s] exit: 0
all tests passed
exit=0
[]
['a', 'b', 'c', 'd']
['the']
`python3 test_wordcount.py` now passes.

The original `wordcount.py` had three bugs:

1. **No normalization** — it counted raw `split()` tokens, so `Go,` / `go!` / `GO?` were separate keys and punctuation leaked into output.
2. **Ascending sort** — `sorted(counts, key=counts.get)` returned the *least* common words first.
3. **Unstable tie-breaking** — Python's sort left ties in first-appearance order, so `"the cat and the hat and the bat"` gave `cat` instead of the expected `bat`.

Fixed version:

```python
import re


def top_words(text, n=3):
    counts = {}
    for word in re.findall(r"[a-z0-9]+(?:'[a-z]+)?", text.lower()):
        counts[word] = counts.get(word, 0) + 1
    return sorted(counts, key=lambda w: (-counts[w], w))[:n]
```

The regex lowercases and strips punctuation while keeping internal apostrophes (`don't` stays one word), the sort key is `(-count, word)` for descending frequency with alphabetical tie-breaking, and `[:n]` slices afterwards. Also verified edge cases: empty string → `[]`, `n` larger than the vocabulary → whole list, and repeated mixed-case words collapse correctly.

Note: `python3 -m pytest` fails with `No module named pytest` — pytest isn't installed in this environment. That's unrelated to the code; the test file's own `__main__` runner works.
Real run with deepseek-flash, 11 s. Output unedited except for redacted local paths and username