Dual Duet

Two gigs in one night: not quite a first (the same happened in Kathmandu many years ago), but perhaps the first representing two completely different projects on the same stage. First up is Khyal Geometries with Shama Rahman on sitar, second is The Printer Jam with Evan Raskob on printer, both sets as part of ACM Creativity and Cognition 2026. The former will be live-coded (Max, Node.js, ClojureScript); the latter will be controllerism.

It’s only now that I realise that my carefully crafted blog taxonomy cannot deal with a single blog post that spans more than one project.

Permalink

rab Coding Agent

rab Coding Agent

2026-07-13 - Building a TUI-based coding agent in Rust from scratch - the agentic loop, tool dispatch, what went well and what didn't, and why I went back to pi
Keywords: rust, coding-agent, agentic-loop, tool-calling, llm, tui, clojure, pi

Permalink

KiCad helpers and caliper improvements

Hi friends,

This newsletter we’re streamlining the process of creating circuit boards and revisiting the DIY electronic calipers project from two years ago.

Kevin’s KiCad Helpers

I first started playing around with electronics via Arduino back in 2010, and I designed my first circuit board back in 2015 for my walnut and leather cell phone. Since then, I’ve made around a dozen PCBs, with a notable spike during the pandemic making weird mechanical keyboards.

Averaging roughly one PCB/year is a maximally frustrating frequency: I have 100’s of hours of cumulative experience, but it’s sufficiently spread across the forgetting curve that every time I start something the details are only vaguely familiar and I’ve got to re-orient myself again. It reminds me of filling out my taxes, where I also furiously consult my notes and attempt to interpret them against new versions of the UI where input boxes have been hidden across other forms and new sub-menus.

Anyway, armed with a coding agent, I externalized everything I kept forgetting how to do in a pile of scripts: Kevin’s KiCad Helpers.

This is tailored to my needs — designing PCBs in KiCad 10 to be manufactured by JLCPCB — but if yours are similar you might find ‘em helpful. Even if you don’t make PCBs, it might be good inspiration for how to apply LLMs to sand down rough edges of whatever convoluted infrastructure is required for your projects.

A quick tour of the tools:

DXF import

I specify all of my mechanical stuff — board outlines, mounting hole positions, etc. — in Autodesk Inventor since it has a constraint solver and allows me to directly reference complex geometry driven by other objects.

KiCad’s GUI has a DXF import tool, but it doesn’t have a mechanism to replace already imported geometry, which makes it tedious to iterate.

This script:

  • imports geometry from a DXF file into a board layer specified by the filename (panel_Edge.Cuts.dxf imports a group ID’d “panel” to the Edge Cuts layer)
  • deletes any geometry previously imported with that ID
  • polls the file for changes, so you get “live reload”

I combine this with a similar poll + export-to-file script in my mechanical CAD tool (shown above) to get live syncing to KiCad (shown below):

DXFs map into KiCad’s origin at top-left of the page outline and the DXF positive Y direction points up, so most of my PCBs end up drawn outside of the page ¯\_(ツ)_/¯.

STEP export

I also need the reverse direction: Import a 3D model of a circuit board and its components back into my mechanical CAD program so I can verify clearances, etc. KiCad has native STEP export, but unfortunately some of the parts have extremely detailed 3D models. My M1 Macbook Air running Windows CAD software in a virtual machine does not do well when every pin of every chip is a separate solid body.

This script generates a STEP export where all parts have been replaced with axis aligned bounding boxes:

Parts database

I design all of my boards to be assembled by JLCPCB, so it’s extremely helpful to have a low-latency way to query their parts. This script downloads CDFER’s daily JLCPCB parts sqlite database and consolidates everything into a single table with a numeric price column and lots of indexes so that searching is fast:

I use DB Browser for SQLite but you can of course use whatever interface you like.

It’s also extremely useful to point LLM agents at this database:

My dude, I need an H-bridge that can drive +/- 30 Volts, please query ~/foo/bar/parts.db and give me a table with 5 options for integrated ones showing price / stock / description. Please also make a table showing options for drivers with external transistors. Include links to the datasheets.

JLCPCB (EasyEDA) schematic and footprint import

Shout out again to CDFER for their JLCPCB KiCad Library, which has all of the “basic” jellybean parts and some of the extended ones as well.

For the parts that are not already available in here, I need to import them. Rather than draw them entirely from scratch, I use uPesy’s easyeda2kicad.py.

However I don’t always want to create an entirely new symbol and footprint if the part actually corresponds to something that’s in the KiCad standard library. So my import tool tries to match existing footprints (including 90 degree rotations) and spawns a terminal UI so you can interactively compare potential matches with the EasyEDA footprint:

Schematic analysis

The most interesting helper I’ve created so far is a general schematic analysis framework, which imports the KiCad netlist and schematic instance properties into a DataScript graph database to run various queries/checks.

For example, this lil’ function calculates the capacitance (of all the explicit capacitors, anyway) on a given net:

(defn net-capacitance
  [db net-name]
  (some->> (d/q '{:find  [?ref ?v]
                  :in    [$ ?net]
                  :where [[?n :net/name ?net]
                          [?n :net/nodes ?node]
                          [?node :node/pin ?pin]
                          [?i :instance/pins ?pin]
                          [?i :instance/ref ?ref]
                          [(clojure.string/starts-with? ?ref "C")]
                          [?i :instance/value ?v]]}
                db net-name)
           (keep (comp parse-capacitance second))
           seq
           (reduce + 0.0)))

This function can then be used to check the total capacitance on, e.g., the power nets, and throw an error if it exceeds, e.g., the maximum 10uF allowed by the USB specification.

(defn check-total-capacitance!
  [db]
  (let [rows (->> ["VCC" "VBUS"] ;;TODO: make this configurable
                  (keep (fn [net]
                          (when-let [c (net-capacitance db net)]
                            {:net net :total-uF (format "%.2f" (* c 1e6))}))))]

    (when (seq rows)
      (print "total capacitance:")
      (clojure.pprint/print-table rows)

      (doseq [{:keys [net total-uF]} rows]
        (assert (< (Double/parseDouble total-uF) 10) (str "Net " net " exceeds USB spec 10uF capacitance"))))))

(It’s easy to accidentally exceed this limit if you keep incrementally adding ICs and their recommended bypass capacitors.)

Since KiCad allows one to add arbitrary key/value pairs to schematic instances, it’s easy to check and print other data as well. For example, I record the i2c address(es) of each chip this way (note i2c and max_mA fields in property inspector on left):

Within the schematic text labels I reference using the KiCad text variable format. (E.g., the above Addr: 0x49 label is defined as Addr: ${U1:i2c}.)

The analysis script throws an error if an address maps to multiple chips:

(defn i2c-addresses
  [db]
  (d/q '{:find [?hex-addr (distinct ?ref)]
         :where [[?instance :instance/ref ?ref]
                 [?instance :instance/attributes ?attribute]
                 [?attribute :attribute/name "i2c"]
                 [?attribute :attribute/value ?addrs]
                 [(clojure.core/identity ?addrs) [?addr ...]]
                 [(clojure.core/format "0x%x" ?addr) ?hex-addr]]}
       db))


(defn check-i2c!
  [db]
  (let [refs-by-addr (i2c-addresses db)]
    (when (seq refs-by-addr)
      (print "i2c addresses")
      (clojure.pprint/print-table (sort-by :addr (for [[addr refs] refs-by-addr]
                                                   {:addr addr :refs (clojure.string/join " "  (sort refs))})))

      (doseq [[addr refs] refs-by-addr
              :when (< 1 (count refs))]
        (throw (ex-info (str "Addr " addr " matches multiple refs: " refs)
                        {:addr addr :refs refs})))

      (println ""))))

This also prints out a helpful table of everything on the bus every time I build the project:

| :addr | :refs |
|-------+-------|
|  0x20 |    U3 |
|  0x49 |    U1 |
|  0x60 |    U2 |
|  0x61 |    U2 |
|  0x62 |    U2 |
|  0x63 |    U2 |
|  0x64 |    U2 |
|  0x65 |    U2 |
|  0x66 |    U2 |
|  0x67 |    U2 |
|  0x68 |    U2 |
|  0x69 |    U2 |
|  0x6a |    U2 |
|  0x6b |    U2 |
|  0x6c |    U2 |
|  0x6d |    U2 |
|  0x6e |    U2 |

(In this example, U2 is an LED driver and exposes each channel on its own i2c address, so its i2c property is specified as 0x60..0x6F.)

Check/build scripts

Speaking of builds, most of the functionality described above is packaged up as a script, so you just run kkh build in a folder and it’ll create an output directory next to every *.kicad_pro it finds in any subfolder. The outputs are named with the date, git revision, and also indicate whether there are unstaged changes in the repository working tree:

2026-07-07-73c5c1
├── bom.csv
├── designators.csv
├── netlist.ipc
├── positions.csv
├── receiver-gerbers-2026-07-07-73c5c1.zip
├── receiver.full.step
├── receiver.simplified.step
└── schematics
    ├── receiver-pcb-back.pdf
    ├── receiver-pcb-front.pdf
    └── receiver-sch.pdf

So one command runs DRC, ERC, and custom analysis checks and then builds all of the output files required to place a JLCPCB assembly order.

The build script also exposes the version string as a KiCad variable, so if you add ${KKH_VERSION_DATE} to your PCB silkscreen, the actual version will appear in the output Gerber files.

I hope by making a proper build script I will never again relive the shame of forgetting to run ERC and having a PCB manufactured where I literally forgot to connect some IC pins entirely…

Caliper updates

About two years ago I had my first foray into digital signal processing and made some electronic calipers.

I haven’t touched the project since I published that post, but a few folks recently asked me about it, and since I had some spare LLM credits I figured I’d spend five minutes sending off the little dude at the problem.

And I literally mean five minutes — these are very rough, rambly dictated prompts that I didn’t even bother to edit. I’m sharing here to remind everyone (especially myself!) that not everything needs to be A Big New Project and sometimes you can have great success from a quick, low-effort attempt.

I spawned a Claude session in the project repo, dictated the following prompt, and went to brush my teeth:

This is a project that I worked on a while ago and the accuracy that came out was alright but I’m wondering if there’s anything I can do to improve the accuracy purely from a computational way rather than making new hardware. Be sure to read the blog post mentioned at the top of the read me to get a background

After I brushed my teeth, it had come up with a few plausible sounding ideas, so I replied with the following and went to sleep:

I’d like you to set this up and come up with a few different ideas. These are pretty good ones and what I want you to do is Wire them up so that I can test them out Individually as a series of experiments with a given protocol and I want you to set everything up as much as possible So I can do it Quickly on my end without having to get in and change the code or anything like that so what you can do is you can make a branch and then Have maybe different entry points or something for each of these different improvements and then Write like an overall program or something like that Which I can just run to test through it and then make it interactive I guess so that I can You know start it up in the hardware Let it sit still for a certain amount of time Move it a fixed amount and back and then You know or some protocol like that and then I’ll tell you what I’m done and then we can just run through that program To test out all of the different ideas and potentially combinations of ideas in like a 10 or 15 minute setting and then We’ll figure out from that which ones are working most effectively

Again, I’m really not trying that hard here.

I woke up the next morning and spent about 20 minutes setting up the caliper PCBs and then running the programs it generated to collect new measurement data. It tried the following improvement hypotheses (LLM-generated text):

knob idea it tests
window size longer coherent integration (noise ∝ 1/√N), incl. one 50 Hz line period
mean_sub remove DC so ADC offset drift can’t leak into the phase
hann suppress spectral leakage from non-integer-cycle windows
smoothing averaging in I/Q (phasor) space instead of phase space; EMA vs block
hysteresis the current 0.1 rad dead-band vs smaller vs none

and based on the initial results and a bit more chatting, the agent proposed an improvement that is, of course, completely obvious in hindsight. My initial parameter sweep (two years ago) used a fixed 2kHz spacing. However, the actual signal and sampling timings generated by the microcontroller are driven by integer divisions of a fixed clock frequency — so most of these sampled timings don’t “line up” nicely in terms of an integer number of signal periods.

Thus, there’s always a bit of DC bias in the signal, which causes undesired noise.

With this change, the LLM-generated experimental code reports a noise floor of around 50um, which is about 10x better than what I was getting before. I’m currently on holiday away from my lab, but I expect it’ll take 30 minutes once I get back to code it up myself and validate.

Anyway, the main takeaways for me are:

  1. Write up your work in blog posts so that both people and LLMs can quickly get context and help you out
  2. There’s still plenty of low-hanging fruit out there to pick, and with LLMs it’s extremely cheap to just ask

Misc. stuff

Permalink

Q3 2026 Funding Survey

Greetings Clojurists!

Please take a moment to complete the Q3 2026 Funding Survey which helps inform our Q3 project awards. It is not a heavy lift - maybe 5 minutes of your time. Your input is invaluable! A link to the survey was sent to your email in the last few weeks - and just in case it made its way to spam, you can look for “We Need Your Input - Q3 2026 Funding”. The survey closes midnight PST on July 15, 2026.

Thanks as always for your support of Clojurists Together and for being a part of this awesome community.

Any questions, please email me at kdavis@clojuriststogether.org

Kathy Davis Program Manager Clojurists Together Foundation

Permalink

Destructuring with computed keys

In Clojure you can destructure a map using an arbitrary expression as the key. For example, here kw is a local binding.

(let [kw :key
      {a kw} {:key 1}]
  a)
;=> 1

Usually this syntax is demonstrated as {sym0 :kw0 sym1 :kw1 ...}, which doesn’t reveal that the keywords are actually in expression position, or an evaluation context. The reason why this more recognizable syntax works is because keyword literals are self-evaluating.

(let [{a :key} {:key 1}]
  a)
;=> 1

The basic rule for expanding these expressions is:

(let [{binding expression} map])
=>
(let [binding (get map expression)])

So that code is equivalent to:

(let [a (get {:key 1} :key)]
  a)

Symbols are not self-evaluating syntax in Clojure, so they must be quoted:

(let [{a 'key} {'key 1}]
  a)
;=> 1

Applying the rule makes the need more obvious:

(let [a (get {'key 1} 'key)]
  a)

Destructuring is pleasingly compositional. This ability to drop down to computed keys makes destructuring available in many more situations than if all keys were required to be statically declared. I found a few examples in my own code and other libraries where this flexibility has been useful.

An example that dereferences the var u/expr-type to compute the key:

(let [{cargs :args
       res u/expr-type} (-> expr-noinline
                            ana2/unmark-top-level
                            ana2/unmark-eval-top-level
                            (check-expr expected opts))]

Another example that uses three class literals as computed keys:

(let [r (reflect-u/reflect cls)
      {methods clojure.reflect.Method
       fields clojure.reflect.Field
       ctors clojure.reflect.Constructor
       :as members}
      (group-by
        class
        (filter (fn [{:keys [name] :as m}] 
                  (if constructor-call
                    (instance? clojure.reflect.Constructor m)
                    (= m-or-f name)))
                (:members r)))]

A snippet of code that destructures nested maps using a mix of keywords, quoted symbols and computed vectors-of-locals as keys. Notice that the vectors are in binding position sometimes to introduce names, then in expression position to perform lookups.


(let [...
      {{[x1 x2] 'x} :fv
       {[y1] 'y [z1 z2] 'z} :idx} remap
      {{{[y1_x1 y1_x2] 'x
         [y1_y1 y1_y2 y1_y3 y1_y4] 'y} [y1]
        {[z1_x1] 'x
         [z1_y1] 'y} [y1 z1]
        {[z2_x1] 'x
         [z2_y1] 'y} [y1 z2]} :idx-context} remap]
  (is (= {:fv {'x [x1 x2]}
          :idx {'y [y1]
                'z [z1 z2]}
          :idx-context {[y1] {'x [y1_x1 y1_x2]
                              'y [y1_y1 y1_y2 y1_y3 y1_y4]}
                        [y1 z1] {'x [z1_x1]
                                 'y [z1_y1]}
                        [y1 z2] {'x [z2_x1]
                                 'y [z2_y1]}}}
         remap)))

You’ve probably seen code like this that destructures booleans from a group-by:

(let [...
      {anns false inits true} (group-by list? normalised-bindings)]

This Malli snippet nests :keys destructuring under a local binding key, method:


(-value-transformer [_ schema method options]
  (reduce
   (fn [acc {{:keys [name qname default transformers]} method}]

And this example elegantly destructures a nested map using keywords and locals, supporting the common pattern of updating a nested value in an atom then destructuring the swapped-in value’s relevant parts.


(defn remove-stale-cache-entries
  [nsym ns-form-str sforms slurped opts]
  {:pre [(simple-symbol? nsym)]}
  (when ns-form-str
    (let [{{{forms-cache ns-form-str} nsym} ::check-form-cache}
          (env/swap-checker!
            (env/checker opts)
            update-in
            [::check-form-cache nsym]
            (fn [m]
              (some-> m
                      (select-keys [ns-form-str])
                      not-empty
                      (update ns-form-str select-keys sforms))))]

Permalink

biff.graph: structure your data model as a queryable graph

The next Biff 2 library is ready to go: biff.graph

biff.graph makes your codebase more understandable/maintainable by helping you split up your code for reading/deriving data into small independent chunks. "Data-oriented dependency injection" is a term I've used to describe the approach.

biff.graph is a lightweight clone/variant of Pathom. I'm a huge fan of Pathom and wanted to include it in Biff by default, however it is a bit of a heavy abstraction and I was concerned if the benefits would be worth the learning effort for people working on small projects.

So biff.graph is an attempt to provide something similar to Pathom in as few lines of code as possible (~600 to be specific, with ~200 for the query engine). You still have to learn the same conceptual model, but learning what exactly it's doing under the hood should be easier. The tradeoff is that biff.graph has less functionality--most significantly there's no query planner, so biff.graph isn't as intelligent about how it executes your queries as Pathom is.

Permalink

libl.in, & Injee, Thanks To Clojure Community. Updates in Clojure Book.

Notes

Permalink

Clojure 1.13.0-alpha3

Clojure 1.13.0-alpha3 is now available! Find download and usage information on the Downloads page.

:select directive in map destructuring

The :select directive binds a name to a subset of the map being destructured containing only the keys mentioned (anywhere) in the binding form.

  • CLJ-2964 :select directive in map destructuring

  • CLJ-2963 Update specs for :select in destructuring

Other changes since Clojure 1.13.0-alpha2

  • RT.map, and thus reader, tracks new PAM thresholds

  • CLJ-1789 select-keys - improve performance (transients, etc)

  • CLJ-2958 ILookup on sets

  • CLJ-2902 pprint - prints arbitary objects in unreadable form

  • CLJ-2801 TaggedLiteral - doesn’t define print-dup

  • CLJ-2269 definterface - does not resolve parameter type hints

  • CLJ-2781 clojure.test/report - docstring has broken references

  • CLJ-2929 zipper - docstring typo

  • CLJ-2901 bytes, shorts, chars - docstring typos

  • CLJ-2811 scalb - docstring links to the documentation for nextDown

  • CLJ-2809 clojure.math/floor - docstring has line that should be on ceil docstring

Try it out

Update your deps.edn :deps with:

org.clojure/clojure {:mvn/version "1.13.0-alpha3"}

Start a REPL with the Clojure CLI (any version) with:

clj -Sdeps '{:deps {org.clojure/clojure {:mvn/version "1.13.0-alpha3"}}}

Permalink

OSS updates May and June 2026

In this post I&aposll give updates about open source I worked on during May and June 2026.

To see previous OSS updates, go here.

Sponsors

I&aposd like to thank all the sponsors and contributors that make this work possible. Without you, the below projects would not be as mature or wouldn&apost exist or be maintained at all! So a sincere thank you to everyone who contributes to the sustainability of these projects.

gratitude

Current top tier sponsors:

Open the details section for more info about sponsoring.

Sponsor info

If you want to ensure that the projects I work on are sustainably maintained, you can sponsor this work in the following ways. If you work for a company that uses my OSS, please ask your employer, that would be even better. Thank you!

Updates

A lot happened in the past two months! Not just coding but also...

Babashka Conf 2026 and Dutch Clojure Days

Three years after the initial installment, Babashka Conf 2026 happened on May 8th at the OBA Oosterdok library in Amsterdam, with David Nolen, primary maintainer of ClojureScript, as our keynote speaker. Thanks to our sponsors Nubank, Exoscale, Bob, Flexiana and Itonomi, to Wendy Randolph for hosting, and to all the speakers, volunteers and attendees who made it such an inspiring day. You can watch all the videos here. Thanks to Ray for recording! The day after, Dutch Clojure Days 2026 rounded out a full weekend of Clojure in Amsterdam, where I did a presentation about ClojureScript and async/await. The video of that is hopefully coming soon.

Babashka Conf 2026 speakers and organizers

Babashka Conf 2026. From left to right: David Nolen, Jen Myers, Adrian Smith, Josh Glover, Rahul Dé, Arne Brasseur, Christoph Neumann, Timo Kramer, Jynn Nelson, Wendy Randolph.

Upcoming: babashka workshop at the Clojure/conj

I&aposm pleased to announce that Rahul Dé and I will be hosting a babashka workshop at the Clojure Conj 2026. The workshop will showcase various use cases of babashka. This hands-on workshop covers the whole lifecycle of a babashka tool, from a quick script to a published, installable CLI app. We assume you know the basics of Clojure and won&apost explain the language itself. Topics include:

  • Setting up your dev environment
  • Managing projects with babashka tasks (bb.edn)
  • A tour of built-in libraries (fs, process, http-client, and more)
  • Writing and running tests
  • Building a CLI with subcommands and automatic help
  • Programming a terminal UI (TUI)
  • Producing a small web app
  • Publishing via GitHub or as an installable tool with bbin

Every concept comes with an exercise, building toward one culminating CLI app. There will be lots of interaction and fun!

Blog posts

Besides this update I published two blog posts in the past two months:

and a ClojureScript reference on async functions:

Projects

Babashka CLI got the most attention this cycle. I added automatic --help generation for dispatch-based CLIs and shell tab completion for bash, zsh, fish, PowerShell and Nushell. There&aposs a dedicated post with a "build your own git" walkthrough linked above. I also made Babashka CLI Squint compatible, so CLIs built with it run on Node.js and in the browser, published as the @babashka/cli npm package. Also ClojureDart support for Babashka CLI got added.

Squint saw a large amount of work that kept going right into early July: a browser nREPL, dynamic vars and binding that survive across separately-compiled ESM modules, an EDN reader, cached lazy seqs, defrecord and a wide set of core protocols, and a big compatibility push to make it pass jank&aposs clojure-test-suite. Replicant now runs on Squint too. I added key diffing to Reagami and did some benchmarks, showing that Reagami on squint performs in the ballpark of React. The benchmark also shows that Replicant on Squint performs even a tad better than on ClojureScript. Not that this makes a huge difference in practice, but it&aposs nice to validate the idea that Squint, for typical apps, can be a valid CLJS replacement while not giving up that much in terms of Clojure features.

A security issue in SCI deserves a callout. A string type-hint could bypass the :classes allowlist and statically initialize any class on the classpath at analysis time. If you sandbox untrusted code with SCI, upgrade to 0.13.53. ClojureDart support and fine-grained interop control (which was needed for cljd support since it has no reflection) also got added. You can now make REPLs for your mobile apps!

Since porting was a theme these past months, I&aposll mention another one: babashka.fs now runs on Node.js via ClojureScript and squint, published as the @babashka/fs npm package.

Here are some highlights per project. See each project&aposs CHANGELOG.md for the full list.

  • babashka CLI: Turn Clojure functions into CLIs!

    • Automatic --help generation for dispatch CLIs, plus shell completions for bash, zsh, fish, PowerShell and Nushell (#112, #24, #95). I wrote a full post on it with a "write your own git" walkthrough: babashka CLI: automatic --help and shell completions
    • Exposed the underlying building blocks so you can roll your own custom CLI parsing: parse-opts*, coerce-opts, validate-opts, apply-defaults, table->tree
    • dispatch now accepts a tree directly (as returned by table->tree), and subcommand order is preserved in printed help and completions
    • Squint support and a new @babashka/cli npm package
    • ClojureDart support (#182)
    • opts->table accepts :columns to override the auto-detected columns (#148, thanks Jan Seeger)
    • Better error messages: negation errors now name the base option, --no-foo on a non-boolean option errors instead of silently coercing, and :edn :coerce now requires an explicit value (#166, #174)
    • Thanks to @lread for a lot of documentation review and general maintenance during this cycle
    • Full changelog
  • Squint: CLJS syntax to JS compiler

    • Browser nREPL support landed, followed by a number of REPL/nREPL fixes: #815 (str wrapping tripping esbuild), #819 (macro changes not picked up in watch mode), #820 (:macros option ignored from JS callers) and #832 (nREPL server hanging on advertised-but-unimplemented ops)
    • The CLI now gets its --help, usage and error handling from babashka.cli&aposs dispatch, plus shell tab completion
    • Dynamic vars and binding now work via a mutable box, safe across separately-compiled ESM modules; syntax-quote resolves symbols through the current namespace and aliases like Clojure. defprotocol got :extend-via-metadata support.
    • reify added
    • clojure.walk added
    • Added squint.edn/clojure.edn with a ~300-line EDN reader
    • Printing is now done through *print-fn*, print, pr and with-out-str, like CLJS
    • Lazy seqs are now cached instead of recomputed on every consumption, matching CLJS&aposs chunked-seq behavior
    • A big push for compatibility with jank&aposs clojure-test-suite: dozens of core functions (sorted-map, hash-map, subvec, pop, merge, keys/vals, peek, transducers, = on dates/regexes/lazy seqs, and more) now throw or behave exactly like CLJS instead of the old loose JS semantics, alongside full built-in cljs.test support
    • #771: dead-code elimination for varargs/multi-arity functions, now emitted via ... spread
    • Replicant support landed, with an example
    • Added defrecord, record? and the IRecord marker protocol. Records store their fields as own string-keyed properties and implement the map-facing protocols, so keyword lookup, keys, seq, assoc, conj and = all work through the regular core functions; the generated implementations are shared runtime functions imported only by files that use defrecord
    • Added a large set of core protocols so custom types participate in the standard functions: ILookup, IAssociative, IMap, ICounted, ICollection, IEquiv, ISet, the transient protocols, and IAtom/IDeref/IReset/ISwap/IWatchable (so a reagent-style reactive atom can be a plain deftype)
    • Compile-time namespace resolution: cljs.analyzer.api/resolve now sees vars of built-in library namespaces like clojure.string, plus :squint/compile-time forms and fixes for macro self-use
    • Full changelog
  • clj-kondo: static analyzer and linter for Clojure code that sparks joy.

    • NEW: macros from source. A defmacro (plus any supporting defn/defn-/def) tagged with {:clj-kondo/macroexpand-hook true} is automatically extracted into .clj-kondo/ and registered as a :macroexpand hook on the next run. See doc/hooks.md
    • Support for async/await in ClojureScript: bumped built-in CLJS analysis to 1.12.145 and added the :await-without-async-fn and :misplaced-async-metadata linters
    • #2822: NEW linter :alias-same-as-ns, warns when an alias equals the namespace it aliases (default :off) (@tomdl89)
    • #2807: NEW linter :conditional-build-up, warns on successive (if pred (assoc m ...) m) rebinding and suggests cond-> (default :off) (@walber-araujo)
    • #2062: NEW linter :if-x-x-y, suggests (or x y) instead of (if x x y) (default :off) (@jramosg)
    • #2818: fix :redefined-var false positive across files declaring the same namespace
    • #2814: fix :protocol-method-arity-mismatch false positive for definterface declaring the same method with multiple arities (@jramosg)
    • #2817: warn on recur inside a vector, map or set literal, since recur is never in tail position there
    • #2854: fix :invalid-arity false positive when an inner binding or fn param shadows a local function name (@yuhan0)
    • Performance work on the rewrite-clj parser and analysis internals: efficient get-in/select-keys, faster sexpr, leaner node allocation (@alexander-yakushev)
    • Deprecation notice: 2026.05.25 is the last release to include the clj-kondo LSP server and VS Code extension; use clojure-lsp instead, which embeds clj-kondo
    • Queued for the next release: early support for the Clojure 1.13 map destructuring keys (:keys!/:syms!/:strs!), including inferring required keys and reporting them at call sites (#2870)
    • Full changelog
  • SCI: Configurable Clojure/Script interpreter suitable for scripting

    • ClojureDart support, with a Flutter REPL example
    • Instance/static method and field overrides plus a :closed allowlist for :classes, giving fine-grained control over host interop; see the interop control docs. Also 1.6x faster instance-method interop on babashka
    • Security fix (sandbox escape): a string type-hint (e.g. ^"some.Class" x) bypassed the :classes allowlist, loading and static-initializing any class on the classpath at analysis time. Only affects sandboxing of untrusted code via :classes; upgrade to 0.13.53
    • Add an :interrupt-fn option: a zero-arg function called on every interpreted fn entry, so host code can interrupt or cancel a running SCI eval (thanks @whilo)
    • Add sci.interrupt/interrupt! to throw an interrupt that sandboxed try/catch cannot catch, and gate finally and the regex functions (re-matches/re-find/re-seq, JVM) through :interrupt-fn too, closing off ways to mask an interrupt and escape the sandbox #1044
    • Fix copy-var incorrectly marking a function as inlined when its unqualified name collided with a clojure.core/cljs.core inlined var (e.g. a custom get), silently breaking with-redefs (@verberktstan)
    • Fix cross-namespace defrecord/deftype type symbol resolution via alias (e.g. (instance? r/Foo x)), fixing nbb#410
    • Fix a self-require (a namespace requiring itself) being reported as a cyclic load dependency
    • Full changelog
  • fs: file system utility library for Clojure

    • Released 0.5.34 with Node.js support (#265): fs now runs on Node.js via ClojureScript and Squint / JavaScript, published as the @babashka/fs npm package. Most functions are supported. The JVM behavior is the reference implementation so all operations are synchronous, and the glob syntax is reimplemented from scratch to match the JVM. File times are BigInt nanoseconds to preserve sub-millisecond precision. zip is left out since Node.js has no native support for it
    • Added spit and slurp on both the JVM and Node.js
    • exec-paths returns [] when PATH is unset or blank instead of throwing
    • @lread did a thorough review pass making the return values of copy, copy-tree, delete-tree, zip/unzip, gunzip and the setters explicit and documented/tested (#197)
  • Babashka: native, fast starting Clojure interpreter for scripting.

    • Working towards a new release integrating all the newest updates in Babashka CLI and babashka.fs. Most importantly I&aposm working on autocompletions added for tasks defined in bb.edn.
    • #1979: fix with-redefs on copied vars (e.g. org.httpkit.client/get) incorrectly treated as inlined
    • Add org.jline.keymap.BindingReader for reading key bindings in terminal applications, completing the input side of the bundled JLine API
    • #1982: add clojure.lang.ChunkedCons, clojure.lang.APersistentVector$SubVector, clojure.lang.ArraySeq, clojure.lang.PersistentVector$ChunkedSeq, java.util.AbstractCollection and java.util.Queue to :instance-checks (@paintparty)
    • Added a terminal tetris example (examples/tetris.clj) built on JLine&aposs Display and AttributedString, showing off the new terminal APIs
    • Full changelog
  • Reagami: A minimal zero-deps Reagent-like for Squint and CLJS

    • Added keyed reconciliation (#40): support :key on children for stable node identity, so diffing reuses nodes instead of recreating them
    • Fixed CLJS :lite-mode compatibility and added it to CI (#41)
    • Added a benchmarks page comparing reagami against CLJS React wrappers and React-free solutions, with mermaid charts to visualize the results (#42, #43)
    • Expanded the README with an ADR on the unkeyed reconciliation algorithm
  • Cream: Clojure + GraalVM Crema native binary

    • I was finally able to reproduce an issue with core.async and filed this upstream
    • Once this is fixed I&aposm going to consider crema more seriously and play with the thought that this could be a substrate for "Babashka next".
  • html: Html generation library inspired by squint&aposs html tag

    • Fixed inline style maps emitting a literal \n between declarations via pr-str, which produced invalid CSS and dropped every declaration after the first (@cycl1st)
    • Only render a map attribute value as CSS when the key is style; other map-like values (e.g. records) now render via str (@telekid)
    • Fixed a symbol-valued attribute resolving to its runtime value instead of its literal name
  • Edamame: configurable EDN and Clojure parser with location metadata and more

    • Added ClojureDart support (non-indexing plain readers matching tools.reader, zero-literal parsing fix, and more)
    • With :auto-resolve-ns, bare syntax-quoted symbols now resolve to the current namespace, matching Clojure&aposs behavior
  • Neil: A CLI to add common aliases and features to deps.edn-based projects

    • #261: neil dep upgrade now upgrades unstable deps (e.g. release candidates) to a newer unstable version when no newer stable version exists
    • Added a README note on brew trust for users who installed neil before Homebrew introduced tap trust
    • The next neil version will make use of the new Babashka CLI features which is already prepared in a PR
  • Nbb: Scripting in Clojure on Node.js using SCI

    • #410: fixed a regression, introduced by the async/await work in #408, where a defrecord/deftype type symbol referenced through a namespace alias (e.g. (instance? r/Foo x)) failed to resolve
  • deps.clj: a faithful port of the clojure CLI bash script to Clojure

    • As always, catching up with the most recent Clojure CLI versions
  • Pod-babashka-gozxing: a babashka pod for QR code and barcode decoding/encoding, backed by gozxing

    • Initial release 0.0.1, installable via the pod registry
  • Graal-build-time: initialize Clojure classes at build time for GraalVM native-image

    • #55: munge package names for namespaces with special characters

Contributions to third party projects:

  • ClojureScript: documented the async/await support from last cycle on the ClojureScript site, including an enhanced reference (#423, #424)
  • Nexus: a data-driven state management library by Christian Johansen. I ported the core engine and test suite to run under squint and added a cljs test runner alongside the existing kaocha setup, so both babashka and squint stay covered in CI (#15, #16, merged)
  • Replicant: a data-driven DOM rendering library by Christian Johansen. I made Replicant itself run under Squint (converting dom.cljs to .cljc, adjusting core.cljc for portability), added babashka/squint test runners and wired them into CI, and fixed a multi-root render bug under squint by switching DOM state tracking to a node-map (#71, #72, merged)

Other projects

These are (some of the) other projects I&aposm involved with but little to no activity happened in the past two months.

Click for more details

Permalink

Clojurists Together Update: May and June 2026

Some of you might know that Clojurists Together are supporting my work on nREPL, CIDER and friends this year. Normally I send them a bi-monthly progress report, but I saw some other people who got funding for their OSS work publish those reports as blog posts for the broader public and I thought to try this for a change.

The past two months were super productive. I had a lot of inspiration during this period and I managed to tackle a lot of long-standing ideas and issues across the entire nREPL/CIDER ecosystem. Funnily enough, I also managed to grow the ecosystem with a couple of brand new projects, but more about those later.

The big highlights from my perspective:

  • CIDER 1.22 is out
  • CIDER 2.0 is essentially ready and needs more user testing
  • Sayid is reborn
  • Two brand new projects saw the light of day: port and neat
  • Piggieback 0.7.0 is out (and Weasel got modernized while I was in the area)
  • clj-refactor and refactor-nrepl got some love as well

Below you’ll find more details about the work I did, project by project.

CIDER

CIDER 1.22 (“São Miguel”) landed in mid-June, wrapping up the 1.x series. Its main features:

  • a registry for jack-in tools, so third parties can plug new build tools and Clojure dialects into cider-jack-in
  • a “default session” escape hatch from sesman’s project-based dispatch
  • keyword-argument versions of the low-level request APIs, alongside a proper decoupling of the nREPL client layer from CIDER’s UI

It also fixed a long list of small annoyances: severe editor lag in unlinked buffers, several TRAMP and SSH tunnel problems, request id leaks, and a bunch of broken menu entries.

Right after that I switched the development version to 2.0 and most of the planned work is already done. The headline items so far:

That last one deserves a special mention: evaluation results that are images now render inline out of the box, and file/URL results offer their content on demand, six years after the feature had to be disabled over its safety problems. There was also a big cleanup pass: consolidated configuration options, the REPL history browser renamed to cider-history to end a long-standing naming clash, theme-aware faces instead of hardcoded colors, refreshed docs and a regenerated refcard. CIDER 2.0 is available from MELPA snapshots and I’d love for more people to take it for a spin before the final release.

cider-nrepl

Lots of cider-nrepl releases, driving the CIDER work above:

  • cider-nrepl 0.60.0 added the ops backing the new protocol exploration commands (cider/who-implements, cider/type-protocols, cider/protocols-with-method).
  • cider-nrepl 0.61.0 brought ClojureScript test support, a ClojureScript macroexpansion fix, formatting that honors the project’s cljfmt configuration, and a pprint backed by orchard.pp.
  • cider-nrepl 0.62.0-alpha1 and 0.62.0-alpha2 hardened the content-type and slurp middleware (URL scheme allowlist, size caps, graceful fetch errors) and cleaned up the response protocol, which is what made it safe to turn rich content on by default in CIDER 2.0.

Along the way the project’s build was migrated from Leiningen to tools.deps, which required a new MrAnderson release (see the blog posts below).

Orchard

Orchard, the library that powers much of cider-nrepl’s functionality, kept pace:

  • Orchard 0.42.0 and Orchard 0.43.0 continued the inspector polish, added symbol classification to orchard.meta, a programmatic listener API for the tracer, and protocol/multimethod introspection in orchard.xref. The project also moved to tools.deps and its CI now covers JDK 26.

Sayid

Sayid, the omniscient Clojure debugger, had been dormant for years and I finally gave it the revival it deserved:

  • Sayid 0.2.0 was the big modernization pass: new mx.cider/sayid coordinates, a documented nREPL middleware API, a consolidated op surface (37 ops down to 26) and fixes for the most annoying Emacs client breakages.
  • Sayid 0.3.0 followed with usability work: no more frozen Emacs during the reload workflow, simpler query commands and help buffers generated from the keymaps.

port

port is a brand new project I started in May: a minimalist Clojure interactive programming environment for Emacs, built on prepl instead of nREPL. It went from nothing to three releases in the course of the month:

  • port 0.1.0
  • port 0.2.0
  • port 0.3.0, which added eldoc with active argument highlighting, a wire-level message log for debugging and a roughly 10x speedup in handling large prepl responses.

I don’t have any particular plans for the future of this project - it was just something that I wanted to experiment with for a while. I see it as an interesting option for people looking for some middle ground between inf-clojure and CIDER.

neat

neat is the other new arrival: a small, language-agnostic nREPL client for Emacs. neat 0.1.0 has the essentials in place: a pure-elisp bencode codec, a comint-based REPL, and a source-buffer minor mode with eval, completion, eldoc, xref and doc lookup, tested against Clojure, Babashka and Basilisp. It’s early days, but it’s a nice testbed for exercising the nREPL protocol outside CIDER.

This project also means I’ve dropped any plans to try to make CIDER a language-agnostic development environment. Going forward CIDER will focus only on Clojure-like languages, and everything else will be covered by neat.

Piggieback and Weasel

The nREPL org saw some ClojureScript-flavored action:

  • Piggieback 0.6.2 and Piggieback 0.7.0. The 0.7.0 release makes load-file evaluate the editor’s buffer contents instead of re-reading from disk, tears down ClojureScript REPLs when their sessions close (no more leaked Node processes) and surfaces ClojureScript status in the describe response.
  • Weasel 0.8.0 modernized the WebSocket REPL: the client now uses the platform’s native WebSocket, so it runs in any modern JavaScript runtime (browsers, Node 22+, Deno, Bun, workers), and the minimum requirements moved to Clojure/ClojureScript 1.12.

I also backfilled proper GitHub releases for the historic tags of both projects, so their release history is finally browsable.

Improving the ClojureScript support in CIDER has long been a major objective for me, and these small changes were some initial steps in that direction.

refactor-nrepl and clj-refactor

refactor-nrepl got three releases: 3.12.0, 3.13.0 and 3.14.0, the last one making the AST-based indexing much faster and more reliable. clj-refactor.el received a round of maintenance on master as well, and will get a new release after I wrap up the work on CIDER 2.0.

I’m still pondering the future of both projects, as I plan to move the most useful refactor-nrepl features (those that don’t carry a lot of complexity) to CIDER and cider-nrepl eventually, and I’m not sure that the flagship AST-powered refactorings are very competitive these days (compared to clojure-lsp and static project-wide analysis a la clj-kondo in general).

I’ll write a bit more about this and I’d certainly appreciate more feedback from the users of clj-refactor on the subject. It’s funny that I’ve been maintaining the project for ages, but I’ve never really used it (mostly due to its brittleness in the past). I think I managed to address some of the biggest problems recently, but perhaps this happened too late and the project has lost its relevance by now.

Blog posts

I wrote a few articles related to the work above:

Wrapping up

Big thanks to Clojurists Together, Nubank and the other organizations and people supporting my Clojure OSS work! I love you and none of this would have happened without you. Sadly, the amount of financial support my projects receive has eroded massively over the past 4 years and I’ve kind of lost hope that this negative trend will eventually be reversed. It was never easy to maintain many popular OSS projects, but the job certainly hasn’t got any easier or more rewarding in recent years…

Overall, a super productive two months. Hopefully the next two are going to be just as productive, although I have to admit I’ve plucked most of the low-hanging fruit already. Then again, I’ve said this many times in the past, so one never knows…

Permalink

Clojure 1.13.0-alpha2

Clojure 1.13.0-alpha2 is now available! Find download and usage information on the Downloads page.

  • Fixes a build problem from new Maven plugins by reverting the change

Try it out

Update your deps.edn :deps with:

org.clojure/clojure {:mvn/version "1.13.0-alpha2"}

Start a REPL with the Clojure CLI (any version) with:

clj -Sdeps '{:deps {org.clojure/clojure {:mvn/version "1.13.0-alpha2"}}}

Permalink

Sayid Redux

I have a soft spot for Sayid - it’s one of the most ingenious Clojure tools ever built, and also one of the most neglected. It’s an omniscient debugger: instead of stopping your program at a breakpoint, it quietly records every call to the functions you’ve traced and lets you rummage through the recording afterwards. It’s the kind of thing you demo to people and watch their jaw drop. And it had been sitting practically unmaintained for the better part of a decade.

Here’s the awkward part: that neglect is largely on me. Bill Piel, Sayid’s original author, handed me the keys ages ago, and I’ve been… let’s say a less than exemplary steward. I’d merge the occasional patch to keep the lights on, but until very recently I’d done precious little to actually move the project forward.

The best time to maintain your open-source project was six years ago. The second best time is now.

– Ancient proverb, lightly adapted

So what finally lit a fire under me? Blame CIDER 2.0. I’ve been reworking CIDER’s debugging and tracing story, and at some point I sat down to make the built-in tracer smarter. A few hours in it hit me: nothing I could realistically bolt onto the built-in tracing would come anywhere close to what Sayid already does. So instead of building a worse Sayid, I dusted off the real one, gave it a good scrub, and here we are - Sayid 0.4.

What is Sayid, anyway?

For the uninitiated: Sayid records the arguments, return value, timing and full call tree of the functions you trace, so you can go back and inspect exactly what happened - no breakpoints, no println, no re-running the thing five times.

While we’re on the subject of embarrassing confessions: I’ve been the maintainer of this thing for years and I still have no idea what “Sayid” actually means or what it’s a reference to. If you happen to know, please, put me out of my misery - I’d love to finally get the joke.

Trace a namespace, run your code, and pop open the workspace with C-c s w:

▾ (demo.coins/can-afford? [:quarter :dime :nickel :penny] 45) => true
    (demo.coins/total-cents [:quarter :dime :nickel :penny]) => 45

can-afford? says true, but four coins worth 41 cents shouldn’t cover a 45-cent tab. Something’s off, and the bug lives inside total-cents. This is where Sayid really shines - flip on an inner trace and it records every expression inside the function:

▾ (demo.coins/can-afford? [:quarter :dime :nickel :penny] 45) => true
  ▾ (demo.coins/total-cents [:quarter :dime :nickel :penny]) => 45
    ▾ (apply + (map coin-values coins)) => 45
        (map coin-values coins) => (25 10 5 5)

There it is, staring right at us: (25 10 5 5), when the last value should be 1. A penny is worth five cents in our coin map. We never wrote a single println, and we never had to guess where to put a breakpoint - we just looked at what actually happened.

That’s the pitch, and it’s a great one. So why was such a cool tool gathering dust?

Why an omniscient debugger, in 2026?

Honestly, part of it is that Clojure folks are spoiled. We have the REPL, so the reflex for most of us is to just re-run a form with some tap> or println sprinkled in and eyeball the output. That works right up until it doesn’t - until the bug is three layers deep in a map over a lazy seq, or only shows up on the 400th call, or lives in code you didn’t write and don’t feel like instrumenting by hand.

A traditional stepping debugger stops the world and makes you drive. Sayid does the opposite: it lets the program run to completion and then hands you the entire execution history as something you can navigate at your own pace. tools.trace is in the same spirit, but it dumps text to stdout - Sayid keeps structured data and gives you a query DSL to slice it. That’s a much better fit for how we actually work in Clojure: run it, capture everything, explore the data.

And here’s the thing that made me want to revive it rather than reinvent it - “capture everything as data and explore it later” is exactly the workflow that’s becoming more relevant, not less. Structured execution traces are gold, whether the thing doing the exploring is you, a data-inspection tool like Portal, or an AI assistant trying to understand why your code misbehaved.

Out with the old coordinates

First order of business was dragging the project into the present.

Sayid used to live under com.billpiel/sayid on Clojars, with namespaces like com.billpiel.sayid.core. The new home is clojure-emacs, so the artifact is now published as mx.cider/sayid:

{:user {:plugins [[mx.cider/sayid "0.4.0"]]}}

I also dropped the personal-domain prefix from every namespace - it’s plain sayid.core, sayid.trace, sayid.nrepl-middleware and so on now. The old com.billpiel/sayid coordinates still get the same releases for the time being, so nobody’s dependency breaks overnight, but the future is mx.cider.

Data first, strings later

Here’s the change I’m most excited about. Sayid’s nREPL middleware used to hand the Emacs client a pre-rendered blob of text plus a pile of text properties for colouring. In other words, the server did all the rendering and the client was a dumb terminal. That single decision is a big part of why there was exactly one Sayid client.

So the middleware now speaks data. There’s a family of new ops - sayid-get-workspace-data, sayid-query-data and friends - that return the recorded call tree as honest, navigable data instead of a wall of text:

{"id"       "4793"
 "name"     "demo.coins/can-afford?"
 "args"     ["[:quarter :dime :nickel :penny]" "45"]
 "return"   "true"
 "file"     "demo/coins.clj"
 "line"     12
 "children" [...]}

The structural bits (ids, names, timings, source location, the tree shape) come across as real nested maps and lists you can walk by key. The captured values are pr-str‘d, since an arbitrary Clojure value can’t always round-trip over the wire - but that’s the only place strings sneak in, and it’s exactly where you’d expect them.

The upshot: any editor or tool that speaks nREPL can now fetch a workspace and render it however it likes, and a REPL one-liner or a Portal tap gets you the same data with zero Sayid-specific machinery. The whole thing is written up in doc/nrepl-api.md, so you don’t have to reverse-engineer the wire format from the Emacs client the way you would have before.

A UI that doesn’t feel like 2015

With the data ops in place, I rebuilt the Emacs UI on top of them. The workspace and the “what’s traced” views are now proper foldable trees, built on CIDER’s new cider-tree-view. You get real folding (TAB), navigation (n/p), jump-to-source (RET), and - my favourite - c i hands the actual captured value straight to CIDER’s inspector, so you can drill into an argument or a return value as a live, navigable object rather than squinting at its printed form.

There’s also a query layer wired into the tree: f narrows to every recorded call of the function at point, i focuses a single call and its subtree. On a big trace that’s the difference between “wall of text” and “actually finding the thing”.

The best part is that the client is now smaller, not bigger - all the tree rendering, folding and value inspection are handled by mature components instead of bespoke code painting text properties by hand. That’s the payoff of moving rendering to the client: the server ships data, and the client is free to be as fancy or as plain as it wants.

Wait, you broke everything?

Backwards compatibility is a promise you make to the users you have. Sayid had exactly one, and reader, I am that user.

– Me, rationalising

Yeah, I did. I went pretty wild with the breaking changes this time around - new artifact coordinates, new namespaces, a reworked nREPL API, a bumped minimum CIDER version. Normally I’d bend over backwards to keep old clients working, but here I made a deliberate call: as far as I can tell, the bundled Emacs client was the only client Sayid ever had. Dancing around imaginary third parties to preserve compatibility nobody was relying on would have just made the project harder to adopt and harder to maintain.

So I opted for sweeping changes that leave Sayid in a much better place to build on, rather than a museum of backwards-compatible cruft. If it turns out I was wrong and you were quietly depending on the old coordinates - I’m sorry, and do let me know, because that’s genuinely useful information.

Take it for a spin

That’s the gist of it. Sayid is alive again, it’s leaner, it speaks data, and it has a UI I’m not embarrassed to demo. What I’d love now is for more people to actually use it and tell me whether the new direction resonates.

So please - [mx.cider/sayid "0.4.0"], trace something gnarly, and pop open the tree. Then head over to the issue tracker and tell me what you think: what feels great, what feels rough, what’s missing. I have ideas for where to take it next (bounding the recording so you can safely trace a whole namespace under a test suite is high on the list), but I’d rather steer by what people actually want out of it.

Big thanks to Bill Piel for building such a wonderful tool in the first place - I’m merely standing on the shoulders of a giant here. And thanks in advance to everyone willing to kick the tyres on the revival!

That’s all from me for now. Keep hacking!

P.S. One more thing…

Well, that didn’t take long. Remember that “high on the list” bit a few paragraphs up - bounding the recording so you can safely trace a whole namespace under a test suite? Turns out I couldn’t leave it alone. A short burst of small improvements after this post went up, and it’s done: Sayid 0.5.

That was the thing that made Sayid feel like a toy - point it at a real workload and it would cheerfully eat your whole heap and fall over. The recording now has a set of tunable bounds instead: a cap on how many top-level calls it keeps, a depth limit, 1-in-N sampling for hot paths, a per-function cap, and a keep-only-the-last-N mode for when what you care about is whatever happened right before things went sideways. Fat and infinite values no longer hang the data ops either, and the traced-functions view got its enable/disable/remove actions back.

The upshot for you: you can finally point Sayid at real code under real load without babysitting it. Same ask as before - give it a spin and tell me how it feels.

Permalink

Copyright © 2009, Planet Clojure. No rights reserved.
Planet Clojure is maintained by Baishamapayan Ghose.
Clojure and the Clojure logo are Copyright © 2008-2009, Rich Hickey.
Theme by Brajeshwar.