lumbergh
v0.1.0
Published
A micromanager for local coding agents
Readme
Lumbergh
A micromanager for local coding agents.
Yeah, if you could just go ahead and create that feature branch, that'd be great.
The Problem
If you've tried running a local LLM as a coding agent on consumer hardware, you've probably seen this:
Agent: "I will tackle TaskPlanner.php next"
You: "OK, do that then..."
Agent: "Do you want me to modify that file?"
You: "Yes!"
Agent: *finally does a bit of coding*Or this:
Agent: "Actually, I'll try ls -la /."
Agent: "Actually, I'll try ls -la /."
Agent: "Actually, I'll try ls -la /."
Agent: "Actually, I'll try ls -la /."Local models like Gemma 4 26B are frustratingly close to being useful coding agents. They can read GitHub issues, create feature branches, break tasks into steps, and write decent code. But they have two crippling habits:
- They need constant handholding. They narrate what they're about to do, then stop and wait for permission.
- They get stuck in loops. Same action, same output, over and over.
Lumbergh is an outer agent that wraps your local coding CLI and manages these quirks automatically. It's a micromanager -- and that's the point.
How It Works
Lumbergh runs your local coding agent through a loop:
+------------------+
| Pre-flight |
| (git, tests, |
| existing PRs) |
+--------+---------+
|
v
+------------------+
+---->| Worker (LLM) |
| | via hook script |
| +--------+---------+
| |
| v
| +------------------+
| | Loop detect |
| | (hash compare) |
| +--------+---------+
| |
| v
| +------------------+
| | Verifier |
| | (git/gh checks) |
| +--------+---------+
| |
| DONE? -----> exit
| |
| FOLLOWUP
| |
+--------------+Each stage except loop detection is a shell script hook that you can swap out for your own workflow.
Pre-flight
Before any LLM inference happens, the default preflight.sh hook checks:
- On main? Fails if you're on a feature branch.
- Clean tree? Fails if there are uncommitted changes.
- Up to date? Pulls latest with
--ff-only. - Existing PRs? Fails if there's already an open PR for this issue.
- Tests pass? Runs
npm test. Fails if tests are broken.
These checks are instant and save minutes of wasted inference when something is wrong.
Worker
The default worker.sh hook invokes opencode with your local model. Lumbergh sends it a task with a short preamble:
RULES: Act immediately, no confirmation needed. Do not run ls on large dirs.
Do not summarize or ask how to help. Tests already pass -- only rerun after
your changes. Finish by: 1) create feature branch, 2) commit, 3) push,
4) create PR referencing the issue. Do NOT close the issue.
TASK: Solve Github issue 29Session IDs are tracked so followup turns continue the same conversation.
Loop Detection
After each worker turn, Lumbergh hashes the output (MD5) and compares it to the last N outputs. If 3 consecutive outputs are identical, it interrupts the worker and forces a different approach.
Verifier
This is the key insight: don't use an LLM to verify LLM work.
The default verify.sh hook uses concrete git/GitHub checks:
- On a feature branch?
git branch --show-current - Changes committed?
git status --porcelain -uno - New commits?
git rev-list --count main..HEAD - PR created?
gh pr list --head <branch>
Each missing step becomes a specific followup instruction sent back to the worker. The verifier is instant, deterministic, and never hallucinates. It must output exactly DONE or FOLLOWUP:<instruction> -- Lumbergh validates the format.
Usage
Run Lumbergh from your project directory:
cd ~/projects/myapp
# First run auto-creates .lumbergh/ with config and hooks
lumbergh "Solve Github issue 42"
# Or initialize first to customize hooks before running
lumbergh init
# edit .lumbergh/hooks/worker.sh to use aider instead of opencode
lumbergh "Solve Github issue 42"
# One-off overrides via CLI flags
lumbergh "Fix bug" --max-turns 5CLI
lumbergh <prompt> [options]
lumbergh init
Commands:
init Initialize .lumbergh/ in the current directory
<prompt> Task description (e.g., "Solve Github issue 42")
Options:
--max-turns <n> Maximum turns (default: 20)
--loop-threshold <n> Consecutive identical outputs before redirect (default: 3)
--preamble <text> Override the default preamble
--version Show version
--help Show this helpPer-Project Config
Each project gets a .lumbergh/ directory (auto-created on first run or via lumbergh init):
myapp/
.lumbergh/
config.json # maxTurns, loopThreshold, preamble
hooks/
preflight.sh # Pre-run checks
worker.sh # LLM invocation
verify.sh # Completion checksConfig precedence: CLI flags > .lumbergh/config.json > hardcoded defaults.
Running lumbergh init on an already-initialized project prints "Already initialized" and does nothing.
Requirements
- Node.js 18.3+
- opencode installed and configured (for default worker hook)
- Ollama running with a model (tested with gemma4:26b-16k)
ghCLI authenticated (for default verify/preflight hooks)npm testworking in your target project (for default preflight hook)
Custom Hooks
Why shell scripts?
Hooks are the boundary between the agentic worker (your LLM doing the creative coding) and deterministic micromanagement (Lumbergh enforcing the workflow). That boundary is intentionally made of bash scripts because bash is the lingua franca of coding agents -- every LLM-based coding tool can read, write, and modify shell scripts fluently. If your project uses pytest instead of npm test, or deploys to GitLab instead of GitHub, you can ask your coding agent to update the hooks for you. The scripts are short, self-contained, and live in your repo at .lumbergh/hooks/ -- a natural target for agentic modification.
Hook resolution
Lumbergh looks for hooks in this order:
.lumbergh/hooks/in the current project directory- Built-in defaults shipped with Lumbergh (fallback if a hook file is deleted)
After lumbergh init, all hooks are copied to .lumbergh/hooks/ for you to edit (or have your agent edit):
| Hook | Arguments | Environment | Expected Output |
|------|-----------|-------------|-----------------|
| preflight.sh | $1 = project dir, $2 = prompt | -- | Exit 0 to pass, non-zero to fail |
| worker.sh | $1 = project dir, $2 = message | LUMBERGH_SESSION_ID | LLM response to stdout |
| verify.sh | $1 = project dir | LUMBERGH_INITIAL_BRANCH | DONE or FOLLOWUP:<instruction> |
You only need to override the hooks you want to change. For example, to use a different LLM runner like aider or claude-code, just replace worker.sh. To adapt for GitLab instead of GitHub, replace verify.sh and preflight.sh.
Architecture
src/
index.ts CLI entry point (thin wiring)
cli.ts Argument parsing (init command + run with flags)
config.ts Per-project config loading + merge with precedence
init.ts Scaffold .lumbergh/ directory
orchestrator.ts Main turn loop with dependency injection
hook-resolver.ts 2-tier hook resolution (project > built-in)
hook-runner.ts Safe shell execution (execFile) + verdict validation
loop-detector.ts MD5 hashing + consecutive-match detection
logger.ts Structured output with configurable stream
types.ts Shared interfaces
hooks/
preflight.sh Default: git checks, existing PRs, npm test
worker.sh Default: opencode invocation with session support
verify.sh Default: git/gh state checksZero runtime dependencies -- only Node built-ins (node:child_process, node:crypto, node:fs, node:path).
The orchestrator receives hookResolver and hookRunner as injected dependencies, making it fully testable without real hooks or shell execution.
Development
npm install
npm test # Run all 99 tests
npm run test:watch # Watch mode
npm run build # Compile TypeScript to dist/