An agent writes a tool, the loop injects a fault, and the agent repairs it. A separate verifier decides whether it passed. The agent does not.
Live demo: https://molt.coey.dev
Each visit creates a fresh Cloudflare Container, runs one self-heal loop, streams it to a terminal in the browser, and destroys the container when it finishes. A run can fail on screen; the verdict comes from an independent verifier, not the agent's own report.
build the agent writes src/tools/generated/wordcount.ts
fault the loop injects an off-by-one on disk
detect a separate verifier runs the tool and observes the wrong count
heal the agent re-reads, repairs, and is re-checked
accept HEALED only when the tool produces correct output on fixtures
Most agent loops trust the agent's own "done." molt routes the verdict through a verifier the agent does not control. Three controls bound what the agent can do:
- Independent acceptance. A verifier separate from the agent runs the tool on fixed inputs and returns pass or fail. The agent cannot mark its own work done.
- Bounded heal. When a check fails, the exact failing case goes back to the agent as the next prompt. Attempts are capped, so a model that cannot find the bug stops instead of looping.
- Scope confinement.
CODEMODE_WRITE_SCOPElimits the agent to one directory, so a wandering model cannot rewrite the harness around it.
Requirements: Bun (or Node 22+) and credentials for a tool-capable model. The default is Cloudflare Workers AI; Anthropic also works.
bun install
cp .env.example .env
# set CLOUDFLARE_ACCOUNT_ID + CLOUDFLARE_API_KEY (or switch to ANTHROPIC_API_KEY)
bun run check
bun run loopYou will watch the agent build a wordcount tool, the loop break it, and the agent fix it. A receipt lands in proof/receipts/<timestamp>.json, the same shape the hosted demo streams. A run can also FAIL: a model that cannot find the bug produces a FAILED verdict, recorded the same way.
browser -> Worker -> Durable Object -> Container (the self-heal loop)
| |
`-- /ai/v1 inference proxy <-------' (agent posts completions here)
`-- env.AI (Workers AI binding) (Worker runs them; no token in the container)
The Worker serves the page, runs the loop inside a per-session Cloudflare Container, and streams structured events back to a browser terminal. The agent inside the container has no Cloudflare credential: it posts OpenAI-shaped chat completions to the Worker's /ai/v1 proxy, and the Worker runs them through its Workers AI binding (env.AI). The container holds only a non-expiring proxy secret. The demo is output-only: the visitor cannot type commands, run a shell, or read the filesystem.
| Primitive | Role |
|---|---|
| Worker | Serves the page, bridges /ws/loop to the container, and proxies inference through its AI binding. |
| Durable Object + Container | One isolated container per session runs the loop and is destroyed after the verdict. |
Workers AI (env.AI) |
Runs the model (@cf/moonshotai/kimi-k2.7-code). No API token leaves the Worker. |
bun install
# set ALCHEMY_PASSWORD and MOLT_PROXY_SECRET in the environment
bun run deploy # builds the worker, then alchemy deployMOLT_PROXY_SECRET guards the inference proxy and is injected into the container; it does not expire, so the demo does not rot. Inference runs through the Worker's AI binding, so there is no Cloudflare API token in the deployment. Put Cloudflare Access in front of the URL before sharing it.
proof/receipts/ holds receipts from real runs, both HEALED and FAILED. Each records the generated source, the injected fault, the independent acceptance result, and every phase. Acceptance is independent of the agent's self-report: a run is HEALED only when the verifier observes correct output on its fixtures.
Behavior lives in one editable file: src/loop/scenario.ts (the build prompt, the injected fault, and the acceptance fixtures).
| Variable | Purpose | Default |
|---|---|---|
CODEMODE_MODEL |
Model id | @cf/moonshotai/kimi-k2.7-code |
CODEMODE_PROVIDER |
pi-ai provider | cloudflare-workers-ai |
CLOUDFLARE_ACCOUNT_ID / CLOUDFLARE_API_KEY |
Workers AI credential (local runs) | none |
ANTHROPIC_API_KEY |
Alternative provider credential | none |
CODEMODE_AI_BASE_URL |
Inference proxy base URL (set by the Worker for the container) | unset |
MOLT_PROXY_SECRET |
Bearer the container presents to the proxy | unset |
CODEMODE_WRITE_SCOPE |
Confine agent writes to one directory | unset |
CODEMODE_DISABLE_VERIFY |
1 removes the agent's bun run check self-verify |
unset |
CODEMODE_MAX_TOOL_CALLS |
Hard ceiling on tool calls per agent turn | 24 |
CODEMODE_PROMPT_TIMEOUT_MS |
Wall-clock cap per loop prompt | 120000 |
| Path | Responsibility |
|---|---|
src/loop/index.ts |
The self-heal loop: build, inject fault, detect, heal, independent acceptance |
src/loop/scenario.ts |
The editable scenario: build prompt, fault injection, acceptance fixtures |
src/loop/session.ts |
One demo session in a throwaway working copy; shared by container and dev server |
src/loop/cli.ts |
bun run loop: run one loop locally and write a receipt |
src/agent/index.ts |
pi-agent-core setup, model resolution, event streaming, tool-call cap |
src/tools/registry.ts |
Base tool registration and generated-tool loading |
src/tools/base/* |
The project-scoped file tools (read, write, edit, verify, list_tools) |
src/tools/base/hashline.ts |
Hash-anchored read rendering and stale-edit rejection |
src/tools/base/path.ts |
Project-root boundary and write-scope confinement |
src/worker.ts |
Worker: page, /ws/loop bridge, /ai/v1 inference proxy, instance key |
src/page.ts |
The single-document demo UI (terminal + structured event log) |
container/server.ts |
Container entry: a WebSocket transport over src/loop/session.ts |
dev-server.ts |
Local server that runs the same session path as the container |
tests/ |
Deterministic boundary tests; no mocked model path |
bun run typecheck
bun run testThe checks cover the edit-safety boundary, write-scope confinement, registry bootstrap, and fault injection. A live model run is deliberately manual because there is no fake inference path.
read renders source with a per-line hash:
12#e3b0c44298|export const tool = {
13#3b18e512d9| name: "bash",
edit must receive those exact anchors. If the source changed since the read, the hash check fails and the agent must read again, which prevents a stale self-edit from landing in the wrong block.
- molt does not sandbox arbitrary shell execution or expose model credentials to the browser.
- The hosted demo is output-only and runs one scripted scenario per session.
- No bundled MCP client, remote skill execution, or multi-agent routing.
Built by Jordan Coeyman as a small open-source exploration of agent self-repair gated by independent verification, on Cloudflare primitives.
MIT