Skip to content

acoyfellow/molt

Repository files navigation

molt

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

The idea

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_SCOPE limits the agent to one directory, so a wandering model cannot rewrite the harness around it.

Run it locally

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 loop

You 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.

How the hosted demo works

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.

Cloudflare primitives

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.

Deploy

bun install
# set ALCHEMY_PASSWORD and MOLT_PROXY_SECRET in the environment
bun run deploy   # builds the worker, then alchemy deploy

MOLT_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

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.

Configuration

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

File map

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

Validate

bun run typecheck
bun run test

The 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.

Hash-anchored edits

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.

Non-goals

  • 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.

Provenance

Built by Jordan Coeyman as a small open-source exploration of agent self-repair gated by independent verification, on Cloudflare primitives.

License

MIT

About

An agent writes a tool, the loop breaks it, and the agent repairs it. A separate verifier decides whether it passed. Live on Cloudflare Containers + Workers AI.

Topics

Resources

License

Contributing

Security policy

Stars

3 stars

Watchers

0 watching

Forks

Releases

No releases published

Packages

 
 
 

Contributors

Languages