biff

2026-03-28T23:57:56.251999Z

a thing that's now in a public repo, though not ready to be "officially" released: https://github.com/jacobobryant/biff.graph I'm a big fan of pathom but I've been hesitant to include it with Biff by default due to the learning curve / it can be difficult to debug pathom and understand exactly what it's doing. this might still have the same learning curve issue (?) since the interface is mostly the same: you make some resolvers, then you can query them. however the implementation is much shorter (few hundred lines) and there's no actual query planning step. so you might miss out on some query plan optimizations, but in return it should hopefully be easier to understand and debug. I've also left out some other things like the plugin system and "lenient mode". this is entirely ai-generated so far and I've yet to even poke around at it with a repl. however I've read all the code, so there are no obvious bugs 😉. next step is to try it out in a couple small ai apps I've been working on. I'll probably have Yakread stick with real pathom since it's a decently sized code base. this is part of gearing up for some big biff releases, so hopefully will have some exciting stuff to put out there soon!

👍 3
👍🏼 2
Gabriel Hawk 2026-04-01T16:11:59.375029Z

This interesting. As someone who has tried getting into Pathom and bounced off, can I ask what the value add is? When you write that it's hard to debug, that was my experience trying to get into it. I had a similar experience using reframe. I think these effect systems that try to separate actions from data can feel very unintuitive sometimes.

2026-04-01T20:14:53.689679Z

In terms of functionality, this doesn't do anything that pathom can't do. So the potential value add is entirely that it's less code/easier to understand. In particular, removing the query planning step means that there's no translation step of eql query -> pathom query plan to understand. This basically just does a breadth-first traversal over your eql query, with enough tricks built in (batch resolvers, caching) so that performance is hopefully still fine (I'm planning to do some measurements later). There's also less opportunity to misconfigure your resolvers since with this, we don't actually need to know if any of your resolvers have nested outputs. e.g. just yesterday I was debugging some claude-written code and found that claude had done something like ::pco/output [:foo/bar] instead of ::pco/output [{:foo/bar [:bar/id]}] which was causing the query to not be executed properly. Whereas with this, you'd always just write ::pco/output [:foo/bar] and it'll work whether that's a regular key or a join key. I also remember dealing with some weird bug where I had a batch resolver that was returning a key that wasn't even in its ::pco/output and it was messing up the rest of the execution for some reason... or something like that. Honestly although I've been spelunking through the pathom code base a few times, I'm far from fully understanding how its query planning / execution works; it's possible if I did understand it all then I'd just tell my past self "oh your conceptual model of how pathom works is just missing X, Y and Z, and once you get that, debugging it is easy!" which brings me to the other big thing IMO which is simply the vastly smaller amount of code as mentioned--400 lines instead of ~9,000 for pathom I believe. So if you're the kind of person who likes to learn libraries by poking through the source code, that'll be much more tractable with this. And hopefully even more after I have a chance to go over this ai-generated code and refine it. One thing I will likely still need to add to this is to make sure that it's easy to see some sort of trace of what's actually happening when the query gets executed, which is a pain point I've had with pathom. e.g. with pathom I'll do a (def env* env) inside a resolver and try to understand what the query-results-so-far look like and that sort of thing... and there is a lot of stuff in that env map ha ha. With this I'd like it do be very easy to, say, set a debug flag and then be able to see "ok we start with {}, then we call this resolver and now we have {:person/id 1}, then we ..." etc. And similarly for exceptions; I want to make sure those have the proper info on them. I am guessing that again, the lack of the query planning step will mean that it'll be easier to get and understand a trace of the execution. I would definitely view this all as experimental in any case! I've sort of hum-hawed for a while about whether I should try to write a minimalist pathom alternative. Only reason I finally did it is because all I had to do to get started was say "please write me a minimalist pathom alternative without query planning" ha ha. > I think these effect systems that try to separate actions from data can feel very unintuitive sometimes. agreed... it's a really interesting design challenge; not just "how do you make this thing pure" but also "how do you do that while still making it feel natural / easy to understand"... once I've used this lib a bit more myself I'd like to try testing it out on some other people. In this vein I've actually got another lib https://github.com/jacobobryant/biff.fx (just made it public now) that's also an attempt to separate pure logic from effects in a hopefully intuitive way... though we'll see about the intuitive bit 🙂 . it's a bit more low-level than biff.graph I'd say but more broadly applicable--biff.fx can take (probably?) any effectful function and split it up into a set of pure functions and minimalist effectful functions, whereas biff.graph is more for the special albeit very common case of when you're reading data from your data model.

🙌 2