Operational guide for agents working in this repository. README.md is the design document; this file is how to build, run and verify the system.
Bendcoder is an autonomous coding agent whose loop is written in Bend2
(bendcoder_agent.bend) with C FFI shims for what Bend cannot express
(subprocess, HTTPS via curl, dirent/stat, mkdir -p). It edits this
repository against a goal, verifies every edit with a test suite, and rolls
back on failure.
- A
bendexecutable on PATH (Bend2 CLI). This machine has nobendbinary; the working invocation isbun ~/work/bend/bend2/main.ts— put a shim on PATH, e.g. abendscript that execs it. Verified against 2.0.5; the local 2.0.9 checkout builds and runs everything cleanly. gcc,curl,bash.ghonly for the delegate scripts.- API keys (below) for any live run; the suite itself needs none.
Two providers: TypeSafe (Classify, the decision calls) and OpenRouter
(Generate, the text the loop asks for). Each key is read from an environment
variable first, then a file:
| Key | Env var | File |
|---|---|---|
| TypeSafe | TYPESAFE_API_KEY |
.env.typesafe |
| OpenRouter | OPENROUTER_API_KEY |
.env.openrouter |
The .env.* files hold the raw key only — not KEY=value. A secrets file
in KEY=value form (e.g. ~/work/.secrets/typesafe.env) must be sourced into
the environment, not copied:
set -a && . ~/work/.secrets/typesafe.env && . ~/work/.secrets/openrouter.env && set +aBoth files are git-ignored. Never print or commit a key.
Smoke-test either provider before a run:
./call_typesafe.sh # curl against the TypeSafe API
./call_openrouter.sh # curl against OpenRouter
./run_bend_typesafe.sh # the Bend-side Classify path end to end./run_bendcoder.sh "Describe the goal here."This compiles bendcoder_agent.bend to bendcoder_loop.c, builds
bendcoder_agent_bin, and runs it. Bend has no argv, so the goal travels in
the environment:
BENDCODER_GOAL="..." BENDCODER_MAX_STEPS=8 ./bendcoder_agent_binEnvironment knobs:
| Variable | Default | Effect |
|---|---|---|
BENDCODER_GOAL |
(required) | The objective; run_bendcoder.sh maps its args here |
BENDCODER_MAX_STEPS |
6 | Step ceiling; each classify→act round is one step |
BENDCODER_VERIFY_CMD |
./run_tests.sh |
Suite an apply_edit must pass to be kept |
BENDCODER_CHECK_CMD |
(unset) | Cheap per-file check run before the suite — <file> in the command names the changed path (else the path is appended). A failed check keeps the draft in place with its error in the state; the suite never runs |
BENDCODER_MODEL |
openai/gpt-oss-120b:nitro |
OpenRouter model for every GenerateText call |
BENDCODER_BASE |
bend base, then ~/bend/bend2/base.bend, ~/work/bend/bend2/base.bend |
Where the index.base_api digest reads base.bend; a leading ~/ expands through HOME |
The agent edits the working tree it runs in. Run it with a clean tree so its
work is a legible diff; a failed verify leaves the draft on disk as the
near-miss the retry repairs against (facts.failed_path names it), so a
kept-but-wrong change is the residue to watch for — ./run_tests.sh after
a run is the check, git checkout . the undo.
A run ends [TASK COMPLETED] when Jev judges the goal met, [STALLED] on a
guard trip (missing key, repeated misses, low-confidence run), or at the step
ceiling.
./run_tests.shThe gate for every edit: C tool tests (-Wall -Wextra -Werror), all FFI shim
sections compiled standalone, every .bend file's #| expectations and
laws, PROOF.bend's path-guard proofs, bash -n over the scripts, a
markdown structure check, and the COVERED: manifest the edit-verify step
consults. Run it before finishing any change.
./delegate.sh 21 12 # issue 21 to Bendcoder itself, 12-step budget
./delegate-devin.sh 21 # the same contract, run by the Devin CLI
./delegate-batch.sh 34 11 # issues in parallel worktrees under ../bendcoder-wt/All three require a clean tree and report the diff plus suite result.
delegate-batch.sh copies .env.typesafe/.env.openrouter into each
worktree because a fresh checkout has none — env-var keys do not need that.
bendcoder_agent.bend— the agent loop: dispatch, guards, read/edit handlersagent_primitives.bend—Classify/Generate, FFI laws and wrappers (P.)selector.bend— the Jev question set, thresholds, route table (S.)action.bend— theActiontype (A.);parse.bend— edit/result parsers (E.)reads.bend— the loop'sBook: read cursors, pending read, miss/low runs (R.)terms.bend— pure goal-term extraction;run_agentgreps the survivors into the initial state before step 1 (T.)base_api.bend— pure base.bend digest:^(def|type)signature extraction, continuation join, namespace filter — injected asindex.base_api(B.)tool_read.bend— pure read implementation with proved lawsguard.bend/LAWS.bend/PROOF.bend— the path guard and its proofstools_c.h— byte-level file algorithms (Read/Write/Edit/Grep)sys_c.c— FFI effects incl.sys.exists/sys.remove_file;typesafe_c.c,openrouter_c.c,json_parse_c.c— HTTP and JSON extractiontest_tools.c— the C half of the suite, gated ontools_c.h
- Read the "Bend Constraints" section of README.md before writing Bend — the
affine/
+markers, no-forward-references, and structural-decrease rules are not what other languages teach, and the checker enforces all three. - Every Bend file ends in
#|lines the suite diffs against its output; update them when behaviour intentionally changes. - Keep orchestration in Bend, byte-level work in
tools_c.h, effects insys_c.c. That split is the architecture; do not reintroduce a C loop. - Keys live in env vars or git-ignored
.env.*files only.