Self-improving code loops for people who ship.
Spec. Patch. Eval. Repeat.
BozoLoop is a zero-dependency, TypeScript-first library for running iterative code improvement loops. You supply a goal, a way to generate changes, and evaluators β BozoLoop handles the loop, ledger, pause/resume, and checkpoint mechanics.
Mental model:
spec β propose change β apply change β run evals β record result β repeat
BozoLoop is not an agent platform, IDE plugin, or opinionated framework. Itβs a tiny composable primitive you plug into your existing workflow.
Most βself-improving codeβ tools are bloated agent platforms that try to own your entire workflow. BozoLoop is the opposite:
npm install bozoloop
import { createLoop, commandEvaluator } from "bozoloop";
const result = await createLoop({
goal: "All tests pass",
workspace: ".",
maxAttempts: 10,
engine: {
async suggest(ctx) {
// Call your LLM, read files, generate diffs β whatever you want
return {
summary: `Fix attempt #${ctx.attempt}`,
patch: { /* your patch data */ },
};
},
},
applier: {
async apply(patch, workspace) {
// Write files, apply diffs, run formatters
return { ok: true, message: "Applied." };
},
},
evaluators: [
commandEvaluator("tests", "npm test"),
],
}).run();
console.log(result.success ? "β
Done!" : "β Failed");
Create a bozoloop.config.ts (or .js) for reusable setups:
import { defineConfig, commandEvaluator } from "bozoloop";
export default defineConfig({
goal: "All tests and type checks pass",
workspace: ".",
maxAttempts: 10,
engine: myEngine,
applier: myApplier,
evaluators: [
commandEvaluator("tests", "npm test"),
commandEvaluator("types", "npx tsc --noEmit"),
commandEvaluator("lint", "npm run lint"),
],
hooks: {
onAttemptEnd: (record) => {
console.log(`#${record.attempt} ${record.pass ? "β
" : "β"}`);
},
},
});
# Run the loop
bozoloop run --config bozoloop.config.js
# Resume a paused loop
bozoloop resume --config bozoloop.config.js
# Inspect state and ledger
bozoloop inspect --config bozoloop.config.js
# Rollback to last checkpoint
bozoloop rollback --config bozoloop.config.js
The CLI is zero-dependency (no commander/yargs). It reads your config, runs the loop, and writes state to .bozoloop/.
| Export | Description |
|---|---|
createLoop(config) |
Create a BozoLoop instance |
BozoLoop |
The loop class (also usable directly with new) |
defineConfig(config) |
Type-safe config helper for config files |
commandEvaluator(name, cmd) |
Create an evaluator from a shell command |
FileCheckpointProvider |
Filesystem-based checkpoint/rollback provider |
| Interface | Role |
|---|---|
SuggestionEngine |
Proposes changes given current context |
PatchApplier |
Applies a proposed change to the workspace |
Evaluator |
Evaluates the workspace, returns pass/fail |
CheckpointProvider |
Creates and restores workspace snapshots |
BozoLoopHooks |
Lifecycle hooks for observability and integration |
const loop = createLoop(config);
await loop.run(); // Run from beginning
await loop.resume(); // Resume a paused loop
loop.pause(); // Pause (takes effect between attempts)
loop.abort(); // Abort (takes effect between attempts)
await loop.rollback(); // Rollback to last checkpoint
loop.inspect(); // Get { state, ledger } data
Your engine proposes a change. It receives the goal, workspace path, current attempt number, and all previous attempt records. Return a summary and a patch (any shape β your applier knows how to interpret it).
Takes the patch from your engine and applies it to the workspace. Could write files, apply git diffs, run code generators β whatever you need.
Run after each patch is applied. All evaluators must pass for an attempt to succeed. commandEvaluator is the built-in helper for running shell commands (test suites, linters, type checkers, etc).
Fire at every stage of the loop lifecycle:
hooks: {
onLoopStart, onLoopEnd,
onAttemptStart, onAttemptEnd,
onSuggestion, onApply, onEval,
onPause, onResume, onAbort,
}
Use hooks to log, send webhooks, deploy previews, notify Slack, or integrate with external systems.
Every attempt is recorded to .bozoloop/ledger.json with full detail:
No mystery behavior. Everything is inspectable.
BozoLoop works great for grinding on an existing codebase:
cd my-project
# Set up your bozoloop config
bozoloop run --config bozoloop.config.js
Use it after Codex/Cursor/Claude writes code β BozoLoop can iterate until tests pass, types check, or any other condition is met.
BozoLoop also works for bootstrapping new projects from scratch. Point it at an empty directory with a spec and let it iterate:
createLoop({
goal: "Create a working Express API with auth",
spec: "REST API with JWT auth, user CRUD, PostgreSQL...",
workspace: "./new-project",
// ...
});
BozoLoop supports basic pause/resume semantics in v0.0.1:
loop.pause() β the loop stops after the current attempt finishes"paused" or "aborted" to .bozoloop/state.json status field β the loop checks this between attemptsloop.resume() or use bozoloop resume from CLIv0.0.1 limitations:
BozoLoop includes a simple filesystem checkpoint provider:
import { FileCheckpointProvider } from "bozoloop";
createLoop({
// ...
checkpoint: new FileCheckpointProvider(".bozoloop"),
});
Before each attempt, the workspace is snapshotted to .bozoloop/checkpoints/. Use loop.rollback() or bozoloop rollback to restore the last checkpoint.
v0.0.1 limitations:
BozoLoop is designed to complement, not replace, your existing workflow:
commandEvaluatorBozoLoop has zero runtime dependencies. The only devDependencies are TypeScript and @types/node for the build. Your node_modules stays clean.
v0.0.1 (current) β Foundation
defineConfigPlanned
bozoloop init scaffolding commandBeing honest about what this version does and doesnβt do:
tsx or compile .ts to .js firstThis is a real, working package β not vaporware. These limitations are honest constraints of a v0.0.1, not missing features hidden behind abstractions.
MIT
bozoloop β because your code should improve itself,
and you shouldn't need a circus to make it happen. π€‘