From 6d896ca2ebce22a9f0b2b71d5d7f7084f3da360c Mon Sep 17 00:00:00 2001 From: Experimenting With Computers <1908752+makefunstuff@users.noreply.github.com> Date: Tue, 22 Sep 2026 14:35:13 +0300 Subject: [PATCH 1/5] =?UTF-8?q?docs(readme):=20surface=20cut=20=E2=80=94?= =?UTF-8?q?=20install,=20smoke,=20harness=20cost,=20links?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 411 +++++------------------------------------------------- 1 file changed, 33 insertions(+), 378 deletions(-) diff --git a/README.md b/README.md index 8534814..0ace2d6 100644 --- a/README.md +++ b/README.md @@ -2,111 +2,50 @@ A Rust CLI that is one stage in a pipeline: prompt and context in on argv/stdin, the answer on stdout, diagnostics on stderr, a verdict in the exit code. Speaks -to any OpenAI-compatible endpoint. Sibling binary `clank-jev` routes and gates; -compose them — do not fold chat/agent UX into `clank`. - -```sh -cargo build --release -export CLANK_BASE_URL=http://127.0.0.1:8080/v1 # your server — no laptop-only default -export CLANK_MODEL=$(curl -s "$CLANK_BASE_URL/models" | jq -r '.data[0].id') -./target/release/clank -m 'reply with exactly: pong' # -> pong, exit 0 -``` +to any OpenAI-compatible endpoint. Sibling `clank-jev` routes and gates — +compose them; do not fold chat/agent UX into `clank`. ![clank in a shell](docs/images/clank-demo.gif) -Recorded against a live server by `scripts/record-demo.sh`: observers, pipe as -context, `--each`, schema through `jq`, failure with reason and `exit 1`. - -**Read next:** [CHEATSHEET.md](CHEATSHEET.md) · [PROTOCOL.md](PROTOCOL.md) +Recorded against a live server by `scripts/record-demo.sh`. -- [docs/use-cases.md](docs/use-cases.md) — job families, gates, prices -- [docs/clank-jev.md](docs/clank-jev.md) — typed routing decisions -- [docs/macbook-omlx-local-inference.md](docs/macbook-omlx-local-inference.md) — - research notes (local Mac models); not required for first run -- [docs/history/](docs/history/README.md) — closed research records -- [.jev/README.md](.jev/README.md) — design invariants as editor rules -- [STATUS.md](STATUS.md) — open items and how to check them +**Site:** [makefunstuff.github.io/clank](https://makefunstuff.github.io/clank/) +· **Cheat sheet:** [CHEATSHEET.md](CHEATSHEET.md) · **Contract:** +[PROTOCOL.md](PROTOCOL.md) ## Install -**Install line 1:** package `clank-cli-app` → binaries `clank` and `clank-jev` -(package ≠ binary). Never `cargo install clank` — that crates.io name is an -unrelated project. +Package `clank-cli-app` → binaries `clank` and `clank-jev` (package ≠ binary). +Never `cargo install clank` — that crates.io name is unrelated. ```sh cargo install clank-cli-app --locked # -> ~/.cargo/bin/clank and clank-jev ``` -Git fallback (pin a tag): +Git fallback: ```sh cargo install --locked --git https://github.com/makefunstuff/clank --tag v0.1.0 ``` -Six direct dependencies, and nothing system-provided beyond a C compiler -(`ring`, for TLS; no OpenSSL to find). - -Prebuilt archives are attached to each -[release](https://github.com/makefunstuff/clank/releases) — linux x86_64, macOS -arm64 and macOS x86_64 — each holding both binaries, this README, the LICENSE, -the cheatsheet and the protocol, with a `SHA256SUMS`-style `.sha256` beside it: +Prebuilt archives: [releases](https://github.com/makefunstuff/clank/releases). -```sh -gh release download --pattern '*x86_64-unknown-linux-gnu.tar.gz*' -sha256sum -c clank-*.tar.gz.sha256 # macOS: shasum -a 256 -c -tar xzf clank-*.tar.gz -``` - -`--locked` holds the dependency graph to the committed `Cargo.lock`; `--tag` / -`--rev ` pins clank itself. That directory is on `PATH` if you have -installed anything with cargo before. From a checkout instead — `src/` is 2.8k -lines: +## 30s smoke ```sh -git clone https://github.com/makefunstuff/clank && cd clank -cargo build --release # -> ./target/release/clank -cargo test # no model and no network: a stub SSE server and a stub - # JSON one, driven by the real binaries -``` - -At run time it needs an OpenAI-compatible endpoint that does tool calling. -llama.cpp's `llama-server` is what it was built against, and the only piece here -that wants a GPU: - -```sh -export CLANK_BASE_URL=http://127.0.0.1:8080/v1 +export CLANK_BASE_URL=http://127.0.0.1:8080/v1 # your OpenAI-compatible server export CLANK_MODEL=$(curl -s "$CLANK_BASE_URL/models" | jq -r '.data[0].id') clank -m 'reply with exactly: pong' # -> pong, exit 0 ``` -Set `CLANK_BASE_URL` and `CLANK_MODEL` (or `--base-url` / `--model`) yourself. -Built-in defaults in the binary are a development convenience — treat a missing -or dead endpoint as a loud failure (stderr + exit 1), not a silent hang. Those -two variables plus the flags in [Configuration](#configuration) are the whole -configuration: no config file, no state on disk. Container instead of a -toolchain: [In a container](#in-a-container). - -## What it is for - -Ask a model about text you already have, and get back something your shell can -act on: - -```sh -rg -n -C3 "userData" src/ | clank --thinking off -m "what does this do?" -git diff | clank --thinking off --max-tokens 400 -m "review this diff, one line per issue" -rg -n "TODO" src/ | clank --each --thinking off -m "one line: actionable now, or not?" -clank --json-schema @schema.json -m "extract the findings" | jq -er . -clank --jsonl -m "summarize" | clank -m "what did you say?" -clank --system @prompts/review-sh.md -c script.sh -m "review the script" -``` +Set `CLANK_BASE_URL` and `CLANK_MODEL` (or `--base-url` / `--model`) yourself. A +dead endpoint should fail loud (stderr + exit 1), not hang. -## Keeping it fast - -### Harness cost (dated) +## Harness cost (dated) **2026-09-22** one-shot on the same Go model (`opencode-go/glm-5.3-flash`), -tools off, prompt `→ pong`. This is **pipe vs agent CLI**, not a quality -benchmark — agent harnesses still pay TUI/runtime tax with `--no-tools`. +tools off, prompt `→ pong`. **Pipe vs agent CLI**, not a quality benchmark — +agent harnesses still pay TUI/runtime tax with `--no-tools`. | | wall median | peak RSS | |---|---:|---:| @@ -115,311 +54,27 @@ benchmark — agent harnesses still pay TUI/runtime tax with `--no-tools`. | omp | 3.4 s | ~371 MB | | opencode | 5.7 s | ~562 MB | -Full method, caveats, and runs: +Method and caveats: [docs/history/clank-vs-agents-2026-09-22.md](docs/history/clank-vs-agents-2026-09-22.md). -The model is the constraint (clank's own share is 1 ms of startup), so the lever -is the tokens you pay for: - -| rule | measured | -|---|---| -| `--thinking off` for mechanical work | 1.35 s → 0.25 s | -| keep the prompt head stable, vary the tail | 10.4 s → 0.21 s (cached prefix) | -| stop reading when you have enough (`clank … \| head -1`) | 7.1 s → 0.6 s | -| one call holding many items beats many small calls | 1.5× on four items | - -Per call: ≈0.25 s fixed, ≈0.02 s per output token, prompt tokens at 1.3 ms cold -and 0.02 ms cached. -[docs/history/research-harness-constraints.md](docs/history/research-harness-constraints.md) -§5.2 has the raw numbers and §5.3 the comparison against headless pi. - -## Unix contract - -- **stdout is data.** The answer, framed `--each` answers, or `--jsonl` events - (`run`, `item`, `tool_call`, `tool_result`, `assistant`, `error`), and nothing - else. A `--jsonl` stream opens with a `run` event naming the model, the - endpoint and the argv (API keys redacted), so a trace says what produced it. -- **stderr is diagnostics.** Tool breadcrumbs, reasoning under `--show-thinking`, - errors. Token accounting stays in the server's log, where it already lives. -- **exit codes.** `0` ok; `1` failure — model, server, IO, a truncated answer, - an empty answer, or any failed `--each` item; `2` usage. Truncation and - emptiness are failures, not results: a stage that reports a success it cannot - back is worse than one that fails. -- **prompt** comes from `-m TEXT`, positional text, piped stdin (stdin is the - prompt only when no other prompt is given), or one line from a TTY. -- **context** is piped stdin, which becomes a context node when a prompt is also - given: a clank trace renders as a transcript, anything else as text. `-c FILE` - (repeatable) loads a saved context — JSON tree, text, or a trace. `-c` nodes - come first, then the pipe; a pipe that reaches the process is never dropped. -- **tools** are offered only when nothing was piped — no stdin, no `-c`, no - `--each` items — because with no evidence to hand over, the only honest answer - about your workspace is one that looked at it. Every lookup lands on stderr. - When evidence was supplied, that is the evidence and nothing else. - `--no-tools` forces the blind case, and the prompt then forbids citing what it - never saw. -- **no colour**, plain text on both channels. `--quiet` drops the breadcrumbs. -- **SIGPIPE restored** (Rust sets SIG_IGN by default), so `clank … | head` dies - cleanly; output is line-buffered and flushed per delta. - -## Context tree - -A context file is plain text (one text node) or a JSON tree: - -```json -[ - { "text": "raw text node" }, - { "file": "path/relative/to/cwd" }, - { "children": [ { "file": "a.md" }, { "text": "more" } ] } -] -``` - -File leaves are read at render time and emitted as `─── path ───` plus content. -A `-c` file is validated strictly, because a file is configuration someone wrote -on purpose; the pipe parses leniently, because it is evidence. - -## Map (`--each`) - -One prompt over every item on stdin, serially, one conversation per item. The -items are the context, so the prompt has to come from `-m`/positional. - -```sh -git log --format=%s -3 | clank --each -m "one line: rewrite in the imperative mood" -find src -name '*.rs' -print0 | clank --each -0 -m "one line: what is this path for?" -``` - -Text mode frames each answer with the item it belongs to, so stdout maps back to -stdin (`awk '/^─── item /{…}'` splits it): - -``` -─── item 1/3 ─── -readme: add install section and fix name clash - -─── item 2/3 ─── -use-cases §9: implement loop and goal in eight lines of shell - -─── item 3/3 ─── -readme: third shorter and remove essay voice -``` - -**An item is text, not a file.** The item is the context, so -`rg -l "TODO" src/ | clank --each -m "summarize this file"` hands the model a -filename and nothing else, and it answers accordingly — measured on oMLX -(2026-09-19): *"This file exists but its purpose is not described in the -provided context."* To summarize files, let the shell read them: - -```sh -for f in src/*.rs; do - clank -c "$f" -m "one line: what is this file responsible for?" `docs/use-cases.md` gained §8 (87 lines) and `PROTOCOL.md` one line, … the risk is -> that the units are unverified in-place — only hand-tested once, where a truncated -> run left a `run` event with no `assistant`. - -With nothing piped, the read-only observers look around inside the container, -each lookup on stderr: - -``` -> search context_lines=0 glob=* ignore_case=true path=. pattern=exit.?code|EXIT_|exit\(2\)|return 2 -< search ok (3019 B) -> list_dir path=. -< list_dir ok (326 B) -``` - -and the answer cited `src/main.rs:10`. - -Four details are not optional. `-i` is what lets the pipe reach it. -`--network=host` makes a model on localhost reachable, and is also the limit of -the sandbox. `:ro` is not decoration: mounted read-only, `touch /w/pwned` -returns `Read-only file system`. And not alpine: clank is glibc-dynamic, and -musl has no loader for it; `ubuntu:24.04` and `debian:stable-slim` both work. - -## Design - -The surface is stdin, stdout, stderr and an exit code. No daemon, no session -store, no memory you did not hand over, so everything a persistent agent harness -would do internally is a pipeline step you can read: `rg`, `git diff`, `jq`, -`sed`, `xargs -P`. The input is visible (the context is exactly the pipe), the -cost is visible (one call is one call, with no loop re-sending a growing -history), and the write stays yours: clank observes and proposes, a gate -decides, you apply. In a container with the tree mounted read-only that boundary -belongs to the kernel rather than to clank's promises, and it bounds what the -model can *write*, not what it can *reach*. - -What it does not give you: memory across sessions, retrieval you did not -construct, or a loop that edits your code while you are away. - -## Verified - -Dated endpoint/model/output records live in -[docs/history/](docs/history/README.md) (and the verification log they cite). Do -not read this README as a substitute for a `v*` release — tag when you want a -shipped artifact; merge to `main` is not a release. - -## Provenance - -Code and docs are largely LLM-written and then read and curated. The commits are -the record of what changed, when, and each of the claims under *Verified* was -run. +| [CHEATSHEET.md](CHEATSHEET.md) | flags, one-liners, herdr compose | +| [PROTOCOL.md](PROTOCOL.md) | invariants, exit codes, events | +| [docs/use-cases.md](docs/use-cases.md) | job families, gates, prices | +| [docs/clank-jev.md](docs/clank-jev.md) | typed routing decisions | +| [docs/history/](docs/history/README.md) | dated measures and closed research | +| [STATUS.md](STATUS.md) | open items | +| [.jev/README.md](.jev/README.md) | repo rules for the editor | + +Local hooks (optional): `git config core.hooksPath .githooks` — see CHEATSHEET / +STATUS for pre-commit / soft pre-push jev. ## License From d92310fc0f0fe27e86e46498c7d4cf13b18ce95f Mon Sep 17 00:00:00 2001 From: Experimenting With Computers <1908752+makefunstuff@users.noreply.github.com> Date: Tue, 22 Sep 2026 14:35:48 +0300 Subject: [PATCH 2/5] docs(readme): flag index so tests/docs.rs stays green --- README.md | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/README.md b/README.md index 0ace2d6..aeeb148 100644 --- a/README.md +++ b/README.md @@ -76,6 +76,20 @@ Token knobs (`--thinking off`, lower `--max-tokens`, `--no-tools` when piped): Local hooks (optional): `git config core.hooksPath .githooks` — see CHEATSHEET / STATUS for pre-commit / soft pre-push jev. +## Flags (index) + +Meanings live in [CHEATSHEET.md](CHEATSHEET.md). Listed so the surface stays +short without drifting from `--help`. + +`clank`: `--api-key`, `--base-url`, `--context`, `--each`, `--json-schema`, +`--jsonl`, `--list-tools`, `--max-rounds`, `--max-tokens`, `--message`, +`--model`, `--no-tools`, `--null`, `--quiet`, `--show-thinking`, `--system`, +`--thinking`, `--timeout`, `--tools` + +`clank-jev`: `--ask`, `--base-url`, `--boolean`, `--checks`, `--choice`, +`--expect`, `--expect-min`, `--json`, `--min-prob`, `--model`, `--print-reason`, +`--provider`, `--quiet`, `--score`, `--timeout` + ## License MIT. See [LICENSE](LICENSE). From 96acea926185abe3bf5fb64d4eef01999b607220 Mon Sep 17 00:00:00 2001 From: Experimenting With Computers <1908752+makefunstuff@users.noreply.github.com> Date: Tue, 22 Sep 2026 14:36:08 +0300 Subject: [PATCH 3/5] docs(readme): list JSONL event kinds for docs.rs --- README.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/README.md b/README.md index aeeb148..3690a28 100644 --- a/README.md +++ b/README.md @@ -76,6 +76,10 @@ Token knobs (`--thinking off`, lower `--max-tokens`, `--no-tools` when piped): Local hooks (optional): `git config core.hooksPath .githooks` — see CHEATSHEET / STATUS for pre-commit / soft pre-push jev. + +JSONL event kinds (`--jsonl`): `run`, `item`, `tool_call`, `tool_result`, +`assistant`, `error`. See [PROTOCOL.md](PROTOCOL.md). + ## Flags (index) Meanings live in [CHEATSHEET.md](CHEATSHEET.md). Listed so the surface stays From 5946abd683dcf571febcd724ab7d5127b818132b Mon Sep 17 00:00:00 2001 From: Experimenting With Computers <1908752+makefunstuff@users.noreply.github.com> Date: Tue, 22 Sep 2026 14:37:15 +0300 Subject: [PATCH 4/5] docs(readme): drop STATUS/.jev surface links; no aspirational loud-fail From 55db9ffb5194ed4c394ca44097d023400cfa56e6 Mon Sep 17 00:00:00 2001 From: Experimenting With Computers <1908752+makefunstuff@users.noreply.github.com> Date: Tue, 22 Sep 2026 14:37:29 +0300 Subject: [PATCH 5/5] docs(readme): drop STATUS/.jev/hooks surface; remove aspirational loud-fail --- README.md | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/README.md b/README.md index 3690a28..824aca4 100644 --- a/README.md +++ b/README.md @@ -38,8 +38,7 @@ export CLANK_MODEL=$(curl -s "$CLANK_BASE_URL/models" | jq -r '.data[0].id') clank -m 'reply with exactly: pong' # -> pong, exit 0 ``` -Set `CLANK_BASE_URL` and `CLANK_MODEL` (or `--base-url` / `--model`) yourself. A -dead endpoint should fail loud (stderr + exit 1), not hang. +Set `CLANK_BASE_URL` and `CLANK_MODEL` (or `--base-url` / `--model`) yourself. ## Harness cost (dated) @@ -70,11 +69,6 @@ Token knobs (`--thinking off`, lower `--max-tokens`, `--no-tools` when piped): | [docs/use-cases.md](docs/use-cases.md) | job families, gates, prices | | [docs/clank-jev.md](docs/clank-jev.md) | typed routing decisions | | [docs/history/](docs/history/README.md) | dated measures and closed research | -| [STATUS.md](STATUS.md) | open items | -| [.jev/README.md](.jev/README.md) | repo rules for the editor | - -Local hooks (optional): `git config core.hooksPath .githooks` — see CHEATSHEET / -STATUS for pre-commit / soft pre-push jev. JSONL event kinds (`--jsonl`): `run`, `item`, `tool_call`, `tool_result`,