A flexible, zero-dependency self-improving loop harness. Capture trajectories → reflect → adapt → persist. Pluggable memory, an independent grader, and fail-safe guards. Runs in Node and Cloudflare Workers, and drops into Claude Code, Cursor, Windsurf and any AI coding platform.
┌─────────────────────────────────────────────────────────────────┐
│ capture ──▶ reflect ──▶ adapt ──▶ persist ──▶ (memory) ──┐ │
│ ▲ the continual-learning loop │ │
│ └──────────────── next run starts smarter ◀───────────┘ │
│ │
│ same model. a SYSTEM that gets sharper run over run. │
└─────────────────────────────────────────────────────────────────┘
Most agents are prompters: you send a prompt, watch, send the next. selfloop is the other thing — a system that prompts itself, checks its own work against a goal with an independent grader, remembers between runs, and learns which of its own moves lose so it stops making them. Extracted from a live trading loop that was silently losing to an overfit backtest; now it self-corrects.
npm install selfloop # or: git clone https://github.com/ryanda9910/selfloopZero runtime dependencies. Node ≥ 22 (uses native type-stripping) or any Cloudflare Worker.
import { Learner, MemoryStore } from "selfloop";
const learner = new Learner(new MemoryStore(), "trades", { minSamples: 6, explore: 0.14 });
// each action carries a coarse "setup" key. fold its outcome AFTER it commits.
learner.resetExploration();
const learn = await learner.load();
if (!learner.gate(learn, "2|SHORT|fear").suppress) {
const score = doTheThing(); // e.g. realized PnL, test pass = +1 / fail = -1
await learner.fold([{ key: "2|SHORT|fear", score }]);
}
// after a while: which of my own setups lose? suppress them.
console.log(await learner.reflect()); // [{ key, n, wins, avg }] worst-first
console.log(await learner.distillSkill("my-setups")); // a .md skill you can commitnode --experimental-strip-types examples/trading.ts # the learner in action
node --experimental-strip-types examples/ci-triage.ts # maker → independent grader → goalBefore — the loop trusts a backtest / its own optimism, and repeats a losing move forever:
if (signal.ok) placeTrade(signal); // no memory of whether THIS kind of trade ever worksAfter — the loop learns from its own outcomes and stops the losers, keeping a slice of exploration so they can recover:
learner.resetExploration();
if (!learner.gate(learn, key(signal)).suppress) { // key = "2|SHORT|fear"
placeTrade(signal);
pending.push({ id: signal.id, key: key(signal) });
}
// on close: await learner.fold(pending.map(p => ({ key: p.key, score: p.pnl })));Same model. The system got smarter.
| Piece | What it does | File |
|---|---|---|
Learner |
capture setup→outcome, reflect, suppress proven losers, distill a skill | src/learn.ts |
runLoop |
maker → independent grader → iterate until the goal holds (hard cap) | src/loop.ts |
| graders | predicate, vote (adversarial majority), shell (run a test/lint) |
src/grader.ts |
Memory + stores |
facts + lessons + last session; FileStore / KvStore / MemoryStore |
src/memory.ts |
import { runLoop, vote, predicate, shell } from "selfloop";
const result = await runLoop({
goal: "tests pass and the config is stable",
maker: (goal, i) => produceCandidate(i),
grader: vote([
shell("npm test"), // a real check
predicate("has retries", (c) => c.retries >= 3), // a different lens
], 2), // BOTH must pass
maxIterations: 5, // fail-safe: never loops forever
});import { Memory, FileStore } from "selfloop";
const mem = new Memory(new FileStore("./state"));
await mem.addLesson("e2e checkout flakes on a webhook race — add a settle delay");
console.log(await mem.render()); // load this into your agent's context each runselfloop is a plain zero-dep library, so it runs anywhere. The repo also ships drop-in agent config:
- Claude Code —
.claude/has averifiersubagent (the independent grader), aselfloopskill, fail-safe permission denies, andCLAUDE.md. - Cursor / Windsurf / Copilot / Aider — read
AGENTS.md(the cross-platform standard) for how to work the loop. - Cloudflare Workers — back memory + the learner with KV:
new KvStore(env.MY_KV). - Any LLM API — wire a grader that calls your model, or spawn a subagent as the checker. The grader is just
(artifact, goal) => { pass, reasons }.
- A suppressor never gates the system off: it keeps an exploration epsilon, and the first candidate of every batch always passes (
resetExploration()before each batch). - Only suppresses a setup after
minSamplesand a losing average — never on a hunch. runLoophas a hardmaxIterationscap — it can't loop forever.- Fold outcomes after the action commits, so a retry never double-counts.
- No runtime dependencies; nothing to audit but this repo.
new Learner(store, "key", {
minSamples: 6, // don't judge a setup until this many closed trajectories
explore: 0.14, // fraction of a losing setup's candidates still allowed through
losingBelow: 0, // "losing" = average score below this
});The model doesn't change. What improves is the system around it: the memory it accumulates, the skills it distills, and the grader that keeps it honest. That's the whole idea — and it's ~300 lines of zero-dep TypeScript.
MIT © ryanda9910