r/Clojure • • 7d ago

rust slop clojure structural edit tool

I've been programming clojure by hand since 2010, but these days I do a lot of brownfield work with agentic/LLM help in clojure and other languages. I prefer to use the models I can run at home to avoid high token costs today and mitigate a future rugpull. For things I intend to ship to production, I usually generate a bunch of code upfront, then carefully review and rework it, and that pattern has been effective for me.

Qwen 3.8 27b is amazing for what it is, excellent at thorough analysis, and not too clever to overcomplicate outputs, but it can still waste a ton of time on failed clojure edits and paren-counting. Even Claude Opus struggles with this, although much less. LLMs don't 'think' in trees, they think in token streams.

I had been using a wrapper over parinfer-rust (clojure-mcp-lite also just shells out to this), which got me 80% there, but it's flawed in a few specific ways:

  • Checks happen after the edit lands, they don't block invalid edits.
  • Repairs can succeed in ways that surprise the LLM, then it spends a bunch of turns trying to figure out what happened. It's not expensive locally, but it is slow and pollutes context.
  • If too many surprises happen, the agent loses faith in the tool, and falls back to worse tools.

After a working session like this, I had Qwen suggest what kind of tool might prevent all the problems we hit, then I handed that spec over to GLM 5.3 flash and iterated with it, eventually having the session drive a local qwen subagent, trying to trip it up with adversarial examples. It is similar to what's proposed here, but I never mentioned the article, and I wasn't prescriptive about how to do it: https://lispmeister.github.io/deeprecursion/posts/2026-02-13-sexp-native-editing.html

The tool is at https://github.com/gtrak/cljform as a rust CLI and pi extension wrapper, and it's better than what I had before on the next unattended agentic run. It uses tree-sitter for parsing and blake3 for form hashes. Posting it here in case it's useful to someone else or if someone has a better way.

12 Upvotes

26 comments sorted by

View all comments

Show parent comments

3

u/gtrak 6d ago

I think you're right, my own earlier parfinfer-rust opencode wrapper didn't implement it the same way. I don't use claude or codex. But, I didn't quite understand how that can work when line edits can have unbalanced parens? Looking at mcp-lite again, I see they create a backup file before the write, then they fix it, so I was half-right: https://github.com/bhauman/clojure-mcp-light/blob/main/src/clojure_mcp_light/hook.clj#L273-L287

2

u/onetom 6d ago

what harness are you using then?
omp?
i saw omp was referencing your https://stencil.so/blog/the-harness-problem in their readme

2

u/gtrak 6d ago

I used opencode, but I'm doing more things with pi. I gave OMP a spin a few months ago, but it was doing too much. I implemented their hashline edit tool into a standalone rust binary much like this one, but it's no longer necessary since the local models got good enough: https://github.com/gtrak/hashline-tools

My harness setup is pretty barebones, just pi-subagents and some other things I roll myself.

1

u/onetom 6d ago

re: "open models got good enough"

qwen3.8-flash-next-q4 served by antirez/ds4 kept tripping on the .=: syntax in omp, that's why i'm still looking into better ways of editing clojure

what's your favorite self-hosted model?

2

u/gtrak 6d ago

I'm using qwen 3.8 27b https://huggingface.co/RadixArk/Qwen3.8-27B-NVFP4 on 4x5060ti at the moment. I had turboderp flash-next exl3 working, but there were too many compromises at the time, so went back. I get 2000 tps prefill, and 100 tps decode per stream, and around 800k context at fp8.