- ↔
- →
to read (pdf)
- I don't want your PRs anymore
- JitterDropper | OALABS Research
- DomainTools Investigations | DPRK Malware Modularity: Diversity and Functional Specialization
- EXHIB: A Benchmark for Realistic and Diverse Evaluation of Function Similarity in the Wild
- Neobrutalism components - Start making neobrutalism layouts today
- June 23, 2026
-
🔗 Hex-Rays Blog IDA 9.4: Smarter Navigation and Quality-of-Life Improvements rss
-
🔗 roboflow/supervision supervision-0.29.1 release
What's new
🚀
KeyPoints.with_nms()— NMS for pose estimationimport supervision as sv key_points = model.predict(image) # sv.KeyPoints key_points = key_points.with_nms(threshold=0.5) # removes duplicate skeletonsDerives axis-aligned bounding boxes from each skeleton's valid (non-zero and visible) keypoints, then applies standard box NMS. Supports
class_agnosticmode and anyOverlapMetric(IOU,IOS). RaisesValueErrorifdetection_confidenceis not set.feat-keypoints-with-nms-compressed.mp4
Notable changes
Bug fixes
-
sv.DetectionDataset.as_pascal_vocno longer mutates bounding boxes (#2341) Previously, every export shifted every bounding box by +1 px in-place. A second call compounded the shift. Fixed by rebinding to a new array; on-disk XML output is unchanged. -
sv.Precisionandsv.F1Scorecorrectly count background false positives (#2331) Predictions on images with no ground-truth objects, and predictions of classes absent from any annotation, were previously ignored. UnderMICROandMACROaveraging they are now counted as false positives.WEIGHTEDaveraging is unchanged. Users should re-evaluate existing metric results after upgrading. -
sv.DetectionsSmootherworks with confidence-free detections (#2333) The smoother no longer raises when detections have no confidence scores. Confidence is averaged over the frames that carry it; tracks without any confidence produceNone. -
sv.Detections.from_vlmis robust to malformed Gemini/Qwen output (#2342) Valid JSON that is not a list, or whose elements are not dicts, now degrades to emptyDetectionsinstead of raisingTypeError. A malformed mask value in Gemini 2.5 responses no longer misaligns thexyxy/confidence/masksarrays. -
sv.JSONSinkserializes NumPy scalars incustom_data(#2334)np.int64frame indices and other NumPy scalars incustom_datano longer raiseTypeErrorat flush time. NumPy arrays are serialized as lists. The file handle closes even when serialization fails. -
sv.approximate_polygonrespects the point-count budget (#2332) The function now returns at mostfloor(N * (1 - percentage))points (minimum 3). Previously it could return more points than requested.epsilon_stepis now validated to be positive. -
COCO export preserves all segments for multi-part masks (#2322) Previously, only the first polygon was written when a non-crowd detection had disjoint mask segments. All polygon parts are now written.
Performance
-
sv.HaloAnnotatoris ~4× faster withCompactMaskdetections (#2339)HaloAnnotatornow uses the same optimized CompactMask paint path asMaskAnnotator. Previously it materialized each mask full-frame; now it operates on the bounding-box crop. Annotated output is unchanged. -
Mask IoU uses less peak memory (#2323) Mask IoU computation now uses matrix multiplication on flattened masks instead of an explicit
(N, M, H, W)tensor. For masks larger than 4096×4096 px, computation promotes to float64 automatically. Results are numerically identical. -
sv.mask_to_xyxyandsv.KeyPoints.as_detectionsvectorized (#2330) Both functions now use batched NumPy operations instead of per-element loops. Outputs are bit-identical.
Contributors
- Ruben Haisma (@RubenHaisma, LinkedIn) — VLM robustness, Pascal VOC export fix, DetectionsSmoother, JSONSink, metrics correctness, polygon budgeting, vectorization
- Agis Kounelis (@kounelisagis, LinkedIn) — HaloAnnotator perf, mask IoU matmul, mask_to_xyxy/KeyPoints.as_detections vectorization, OBB cookbook
- Piotr Skalski (@SkalskiP, LinkedIn) —
KeyPoints.with_nms() - Abdelrahman Gomaa (@abdogomaa201099, LinkedIn) — COCO multi-polygon export
Full Changelog :
0.29.0...0.29.1 -
-
🔗 tintinweb/pi-subagents v0.10.4 release
No content.
-
🔗 HexRaysSA/plugin-repository commits sync repo: +1 plugin, +1 release rss
sync repo: +1 plugin, +1 release ## New plugins - [DriverBuddyReloaded](https://github.com/voidsec/driverbuddyreloaded) (2.0.0) -
🔗 Will McGugan Stop, don’t Slop rss
So you find a bug in an Open Source project and wish to spend your tokens solving that issue for the good of humanity. Your fingers hover over the keyboard, trembling in anticipation of the glorious prompt that will unblock your fellow developers. Before you type “FIx issue X make no mistaks” Stop!
AI is a modern marvel, but a Thanos level click of the fingers it is not. To prompt the best fix you need context. You need to diagnose the issue while considering the broader implications of your changes. I’m a maintainer of several open source packages, and let me tell you that well over half of issue reports incorrectly attribute the source of the bug and they often have grave misunderstandings about what the correct behavior is. No matter how good your AI is, if you give it a bum steer it may produce garbage.
I’m faced with a few options for AI PRs, which I will list in order:
- Review them.
- Merge them without a full review.
- Reject them.
Option 1 takes work, especially given AI’s tendency for loquacious bloviation. To figure out if the mechanical megabrain has solved the issue correctly requires that I solve it myself. The time taken to do this dwarfs the time taken to type the prompt.
Option 2 would work for a while. Users would be happy—for a while. But each change that strays from the big picture design or introduces an edge case or compromises an invariant, degrades the code. It’s like taking a photocopy of a photocopy. Fuck, that dates me. It’s like taking a JPEG of a JPEG.
Option 3 makes me look like the jerk. I’m not the jerk. You are not the jerk. There is no jerk here, both have good motivations. Unless the PR was submitted by your OpenFlaw, sorry, OpenClaw. Then you are the jerk. You are spending silly money to inconvenience Open Source maintainers, and I have no time for you.
Realistically, option 3 is the only practical solution for now.
I’d like to clarify that I’m not an AI doomer. We developers will all have to come to terms with a new way of working. But for libraries used by millions you need to be precious about every line, and that can only be done by a knowledgeable human or an AI under supervision of such a human. If you want to use AI to contribute to any of my projects I ask that you know enough that you could have written every line, even if AI did the grunt work for you.
Before I go, let me point out that every project described as being created by AI ships with the majority of code in dependencies created by mammalian brains; imperfect, squishy, sometimes cranky, brains consisting of water, lipids, and protein. I don’t know for how much longer that will be the case, but while it is: Stop, don’t slop.
-
🔗 Armin Ronacher The Coming Loop rss
I don’t prompt Claude anymore. I have loops running that prompt Claude and figuring out what to do. My job is to write loops.
— Boris Cherny
Over the last months I have watched more and more people build something on top of coding agents that feels meaningfully different from just using a coding agent. Some of this happens on top of Pi which is cool to see for sure! The pattern is the same everywhere though: work is put into a queue of sorts, a machine picks it up, attempts it, stops, and then some harness decides whether that was actually the end.
If not, the harness continues the same session, injects another message, starts a fresh session with modified context, or sends the task to another machine. The task stays alive beyond the point where the model by itself would normally have said: "I am done."
I think about that type of loop more than I want to admit.
There is already an agent loop inside every coding agent. The model calls a tool, incorporates the result, calls another tool, reads a file, edits a file, runs tests, and eventually produces some answer. That loop is one we have been quite familiar with for a long time. The other loop is the harness level loop: the loop outside the agent loop. That loop is also not new. We have been doing versions of this since early Claude Code days, but that loop is becoming ever more present in agentic engineering and in recent weeks it has started to dominate the Twitter discourse.
I Am Not Good At This Yet
My current status is that I have not had much success with this way of working for code I deeply care about which turns out to be quite a lot of code.
Part of that is taste and part of it is control. I attempt to set a high bar for what I want code to look like, and I want to understand the code I ship. Under pressure, or in a discussion with another human, I want to be able to explain what the system does without first having to ask a clanker to explain it to me. Now there is obviously a question if this desire to understand the code is one that I will still have a few years from now. For now I have not moved past the point of comprehension being important to me.
Given this desire, there is something I lack with my experience of code written without me paying attention, particularly from loops. Present-day models tend to produce code that is too defensive, too complex, too local in its reasoning. They avoid strong invariants. They add fallbacks instead of making bad states impossible. They duplicate code, invent bad abstractions, and paper over unclear design with more machinery. Worse though: I so far see very little progress of this improving. If anything, on that front it feels to me that we might even be making steps in the wrong direction. At least for my taste, present-day hands-off harnesses like Claude Code with ultracode produce worse code than what we were producing last autumn. That's because Claude Code, with Fable for instance will be working uninterrupted on a problem for thirty minutes or more, when previously the process would have been much more human in the loop.
Furthermore it's well understood that models tend to observe some local failure and add a local defense. Karpathy mentioned how they are “mortally terrified of exceptions”. In systems with important invariants, especially persisted data formats or core infrastructure, the right fix is not "handle every malformed case." The right fix is to make the malformed case unrepresentable or impossible to write in the first place. Yet even with a lot of manual steering, that type of code does not come out of LLMs naturally, and even if the code comes out naturally like that, they will still attempt to handle now impossible errors.
When you take that behavior and you put it behind loops, you tend to amplify it. If each iteration adds another small defense, the system slowly becomes less understandable while appearing more robust. The more hands-off you are, the more that happens. It also teaches really bad practices when tools like this are given to juniors without clear guidance. Because if you ask them, why they are doing all that, they will convincingly argue their case.
Where Loops Work
At the same time, it would be dishonest to pretend the loop pattern does not work because it already works astonishingly well in some domains.
Porting code one of them. There are already impressive examples of large automatic porting efforts, including the reported work around moving parts of Bun from Zig to Rust. I have used it with success myself to port MiniJinja to Go. Performance explorations are another case where this works beautifully. A machine can try experiments, benchmark them, discard failures, and keep searching. Security scanning fits naturally too and so does almost any type of research: asking a system to explore a complex problem space and report back without necessarily committing lasting code. One thing that many of these have in common is that they either do not generate new code, but transform code that already exists, or they produce code that intentionally does not have a long shelf life. They either produce proof of concepts or ideas, surface findings or are more akin to mechnical transformation.
I believe that loops that produce artifacts without necessity of longevity or that create some form of clearly verifiable mechnical translation matters more than the general ability of a harness to mechanically measure a goal. Many successful applications of loops use another LLM as a judge or as an orchestrator. The mechnical translation case can be verified with a binary test case, but it can also be judged by an LLM instead!
Claude Code, for instance, is increasingly good at creating entire experimental workflows that it will then execute. Sure, the code it produces is slop, but that's more the fault of the model than the harness not being a good judge on if a step in the workflow resulted in a net improvement or completion.
The harness just needs some signal that lets it continue. It does not have to be objective or binary, it just has to be useful enough to drive another iteration.
I absolutely love loops already that take the boring parts out of my day to experiment and measure and to give me ideas.
Software As Organism
On the other hand using that same looping methodology to write lasting code does not yet sit well with me. The metaphor I like to reach for is one of moving from software as a deterministic machine to software as an organism.
I became a software engineer in an enviornment that encouraged me to understand the machine. There was always a layer you could peel off to deepen your understanding. Machines that did not exhibit deterministic observable behavior were maybe accepted, but generally seen as not exactly optimal. Software architecture-wise, I saw it as desireable to push further towards more determinism rather than less. Likewise the ability to understand the code has been an undeniable goal. In practice not always possible we still took pride in writing code so that it became possible even for new engineers to navigate complex code bases through clever architecture. On well designed systems there were always engineers that knew where the invariantes lived, which parts were load-bearing and which changes were safe. Ideally all of that was also well documented. Where that understanding was lacking, it was generally regarded as something to improve upon.
Obviously that ideal has always been strained. Many software systems, especially very successful ones had periods where engineers on the team were able to keep them clean. Large software systems are not infrequently too big, too dynamic and too dependent on external services to fit into anyone's head. Even without LLMs we already diagnose distributed systems somewhat like doctors in that we observe symptoms, form hypotheses, "order more tests", try some remedies, and observe again.
Yet with LLMs we're pushing much further in that direction and much quicker. We use them to write the code and we also use them for diagnosis and remedy. There are plenty of engineers that already live in a world in which the first step after the occurrence of a production issue is followed by having a clanker read logs, propose root causes and proactively put up a patch. The resulting patch is then often picked up by another machine that reviews, sometimes even landing it on main without any human supervision.
Obviously that is powerful and I cannot deny that it sounds appealing. But giving in to that idea, particularly with less and less human oversight means accepting that we may no longer understand the whole system in the same way. We treat it, we monitor it, we stabilize it, but we do not necessarily comprehend it.
I have no doubts that for some software, that is okay. Not every line of code deserves human authorship and worse code might have been written in the past.
But do I want all software to be authored this way?
You Cannot Quite Opt Out
What's very uncomfortable is that opting out of this fully machine-driven future may not be an option.
Security is the clearest example today. Even if you do not use loops to build your software, other people will use loops against your software. Attackers will run machines continuously and even if it's not attackers, then security researchers will and some of that automated work will throw up dust but also find real issues. And both the signal and the noise will come your way at a volume that makes it almost impossible to deal with unless you yourself throw a machine at the problem.
Daniel Stenberg's post about curl's summer of bliss is a good example of the pressure maintainers are already under. As far as I know, AI does not play a tremendous role in the core development of curl today. Yet despite all of this, maintainers are overwhelmed by reports, most of which are now AI-generated ones.
If attackers and reporters loop, defenders will eventually need to loop too to keep up. Maybe not to write patches directly, maybe just to triage and reproduce and pressure will increase.
The same is true competitively as some teams will out-build others through raw speed. Some projects will suddenly move faster because a tiny group figures out how to orchestrate machines effectively. Some startups will do with five people what used to require fifty. Some people might literally put a machine against your product in a loop and ask it to "make it like the other one." And if their users are happy, does it really matter?
Not all software will be equally affected. Some domains will punish sloppiness and demand trust and responsibility, but a lot of software lives in a world where raw speed, quick experimentation, and vast coverage matter enormously.
Building New Dependencies
The scariest part to me is that we become dependent on these new machines in new ways. Software has always depended on tools. I remember the time when I had to pay for compilers. These new tools are a flashback to times where creating software came with real costs. But now it's no longer a one-time payment, it's a constant dependency. Not just a dependency on a filled wallet, but also a cognitive dependency.
If a codebase is produced by loops, reviewed by loops, patched by loops, and kept alive by loops, what happens when you no longer have access to the same class of systems? What happens when some trade restrictions take away access to the most powerful models? What if just the cost becomes unbearable? What if you and your team just lose the last remaining ability to understand the code without using the machine?
We may create codebases that are not merely hard to maintain by humans, but that assume machine participation as part of their maintenance model. This is already happening! It's not happening everywhere, and it might not even be happening in ways that are seen as problematic, but we see more and more of it. People more and more merge code they cannot fully explain. People lose their ability to create issue reports or discuss things in chat, without augmenting or rephrasing their messages with the context provided by a clanker. Too many people increasingly rely on a machine to summarize or contextualize it. More and more do I encounter people who converse with me through the indirection of an LLM.
Again, maybe that is not even going to be wrong, but it's a massive change to how we did things.
Future Harnesses
I have little doubt that this is where things are going but going there will require us to do something about our tooling everywhere, and not just in the coding agents.
Just orchestrating more loops won't be enough. Better visualizations of changes or orchestration or agents will not restore our understanding. Either we need to find clever ways to jolt the human back into the loop and make the changes of the loops legible long term, or we need to find better ways to compose these ever more complex systems.
This is also where my thinking about the role of Pi is changing. Pi has been cautious, and I think that caution is good. I do not want a future where every interaction turns into an uncontrolled swarm of machines making changes I cannot follow. I would not want Pi to become an unmaintainable mess in an effort to win the race towards software that writes itself and I would not want Pi to promote this type of engineering either. At the same time Pi is a harness and harnesses are at the center of people running these new types of experiments.
Task queues for coding tasks, orchestration of agents, subagents, durable sessions will matter more and more. Even those of us who have their reservations and are not blindly embracing loops will have to start doing those experiments. We need to, because we need to understand how to make this future bounded and survivable.
Controlling Loops
As you can read from this post, I'm very uneasy about this future. Not cause of fear, but because of caution given experiences with this technology so far.
Adopting the idea of harness loops means that the harness decides when work is finished. In the agent loop, the model eventually says "done" and I review. Even before that, I usually steer along the way. I am involved and I enjoy learning along the way. In the harness operated loop I'm not sure what my role even is. Even the "done" signal loses all meanings and just becomes communicated to yet another machine that judges. My role is reduced to that of a messenger.
Today I do not like much of the code that I see from systems built that way and neither do I enjoy interacting with too much of software built with AI assistence. Looping is powerful but it removes responsibility more and more, and it at least today very much encourages us to give in to the machine.
And yet I have no doubts that this looping future is going to be our future despite the fact that I presently resent it. I already see astonishingly small teams building at impossible speed and I see codebases turning more and more into obscure and confusing organisms that can only be diagnosed by more machines. Those codebases are simultaniously useful and messy.
So I guess I'm coming to terms with that the question is not whether we will loop because clearly we will. Maybe the question is that in a future of loops, how do we don't abdicate judgment, how we can retain rules of good engineering, how we can ensure that responsible human can continue to supervise, how we need to re-think how we architect code to retain sanity along the way.
-
- June 22, 2026
-
🔗 IDA Plugin Updates IDA Plugin Updates on 2026-06-22 rss
IDA Plugin Updates on 2026-06-22
Activity:
- capa
- ghidra
- d70f969e: GP-6773: Fallback loader fix for batch importer
- ida-domain
- ida_graph_parser
- d11b58f3: Summary Algorithm Notebook (#3)
- symbolicator
-
🔗 Simon Willison Porting the Moebius 0.2B image inpainting model to run in the browser with Claude Code rss
This morning on Hacker News I saw Moebius: 0.2B Lightweight Image Inpainting Framework with 10B-Level Performance, describing a small but effective inpainting model - a model where you can mark regions of an image to remove and the model imagines what should fill the space. The released model required PyTorch and NVIDIA CUDA, but since it described itself as 0.2B I decided to try and get it running using WebGPU in a browser. TL;DR: I got it working, and you can try the demo at simonw.github.io/moebius-web/. Read on for the details.
The finished tool
Here's a video demo of the finished tool:
You can open any image in it (non-square images get letterboxed), highlight areas to remove, click the "Run inpaint" button and wait for the model to do its magic.
A parallel agent side-project
My main project for today was landing a major feature in Datasette: a UI for creating and altering tables, as a follow-up to the insert and edit rows feature I released last week.
I was working on that in Codex Desktop (here's the PR) and often found myself spending 5-10 minutes spinning my fingers waiting for it to complete a mid-sized refactor or add the finishing touches to a change to the UI.
(An amusing thing about coding agents is that the harder a problem is the more time you have to get distracted while you wait for them to finish crunching!)
So I decided to spin up Claude Code in a terminal window and see how far I could get at porting Moebius to the web.
Some agentic research to kick off the project
My first step was to ask regular Claude about the feasibility of this project. In Claude.ai, which has the ability to clone repos from GitHub:
Clone https://github.com/hustvl/Moebius/ and tell me if they published the code and weights to run this model anywhere(I hadn't spotted the link to the weights yet, that's tucked away in the "News" section.)
Then:
For Moebius what are the options for running it right now - Python and NVIDIA CUDA only or other options too?And:
Muse on the feasibility of porting it to Transformers.js or similar and running it in a browserI like telling models to "muse on X", it's the shortest way I've found of expressing that I want them to contemplate a problem for me without providing them with a concrete goal.
Here's that chat transcript. I copied out the last answer and saved it as research.md for Claude Code to read later.
Claude suggested using ONNX Runtime Web on the WebGPU backend - the layer below the Transformers.js library I had suggested.
That was enough to convince me it was worth setting Claude Code loose and seeing how far it could get.
I usually start projects like this by gathering as much information as the coding agent might need as possible. Since I didn't expect this project to actually work I did everything in my
/tmpfolder:cd /tmp mkdir Moebius cd Moebius # Grab the Moebius python code git clone https://github.com/hustvl/Moebius # And the model weights (Claude figured this out): GIT_LFS_SKIP_SMUDGE=0 git clone \ https://huggingface.co/hustvl/Moebius Moebius-weights # Finally a couple of libraries we might use: git clone https://github.com/huggingface/transformers.js git clone https://github.com/microsoft/onnxruntime
Setting off Claude Code
I created a directory for the rest of the project and ran
git initin that so Claude could start committing code notes:mkdir /tmp/Moebius/moebius-web cd /tmp/Moebius/moebius-web git init # Copy in that research.md from earlier git add research.md git commit -m "Initial research by Claude Opus 4.8"
I fired up a
claudeinstance in the/tmp/Moebiusfolder, the level above all of the research materials I had prepared for it. I prompted:Read ./moebius-web/research.md - your goal is to port this model to ONNX and WebGPU so we can run it directly in a browser, with a simple UIAs it started to work I dropped in this follow-up (typos included):
Bulid this in /tmp/Moebius/moebius-web and commit early and often, also maintain a notes.md file in there with notes about what you figure out along the way - also start by writing out a plan.md in there and update that plan as oy work tooI often ask agents to keep notes like this - the end result is often interesting, both for myself and for the next agent session that touches the same project. Here's what that notes.md file looked like at the end of the project.
I kicked it off and went back to my main project, checking in occasionally to see how Claude was doing. When it looked like it might have something that worked I prompted:
Tell me what URL I can visit in my own browser to try thisThen I tried it out in Chrome and pasted some errors (and screenshots of errors) back into Claude Code.
After a few rounds of this we had something that appeared to work! Time to put it on the internet so other people could use it.
How would we publish this to Hugging Face such that the model weights were on there and the HTML demo would show up in Hugging Face spaces?Claude Code knows how to use the
hfCLI tool, so I created a model repo on Hugging Face, then created a token that could write to that repo and dropped it into a/tmp/Moebius/token.txtfile so Claude could use it.It published the 1.24GB of converted ONNX weights to huggingface.co/simonw/Moebius-ONNX for me.
I'd seen other demos load weights into the browser from Hugging Face before, so I knew it was possible. I decided to host my own frontend code on GitHub Pages, so I said:
I want to publish the moebius-web folder to GitHub, minus the large files (so maybe minus the models/ folder), such that when I turn on GitHub Pages for that repo navigating to https://simonw.github.io/moebius-web/ serves the UITelling it the final URL was important in case it needed to fix the URLs in the demos that it was building so they would work when deployed to production.
After a few more rounds of iteration, in between working on my main project, we got to a working, deployed version!
Except... each time I reloaded the page it seemed to download ~1.3GB of model weights. Browser caching seemed pretty important for this!
anything clever we can do with serviceworkers or similar to help cache this stuff? It seems to reload every time, I am concerned that there might be something weird about the way HF redirects work that mean we don't benefit from browser cachingI knew that Transformers.js projects could handle this properly, so I grabbed a copy of the Whisper Web demo, dropped it into
/tmp/Moebius/whisper-weband said:look in /tmp/Moebius/whisper-web (with a subagent) and see how they do thisThat project was entirely obfuscated, built JavaScript files so I figured using a subagent would avoid spending the rest of my top-level token context deciphering those files.
Claude figured out that it was using
caches.open("transformers-cache")- the CacheStorage API - and added that to our project.I've shared the full Claude Code transcript for this project (published using my claude-code-transcripts tool).
What did I learn from all of this?
This definitely counts as vibe coding: I didn't look at a single line of code from the project, restricting my input to testing, suggesting small feature improvements (like a progress bar for the large file downloads) and pointing the model in the direction of examples of how I wanted things to work.
Since I didn't write any code the amount I learned about the underlying technologies - WebGPU, ONNX, and the Moebius model itself - was very limited.
As is usually the case with this kind of project the most important things I learned concerned what was possible:
- Claude Opus 4.8 is capable of converting a PyTorch model to ONNX, publishing the result to Hugging Face and then building out a web application and interface that can load and execute that model.
- Chrome, Firefox and Safari are all now capable of running this kind of model - I tried it in all three.
- The CacheStorage API works with ~1.3GB model files.
- ... which means we can have inpainting as a feature of a client-only web application! (If our users can tolerate the 1.3GB download.)
I felt like I should probably try and learn a little more about my project. I fired up Claude.ai and prompted:
Clone https://github.com/simonw/moebius-web/ and use it to teach me all about the model and ONNX and the process of converting a model to ONNX and WebGPU and basically everything I'd need to know in order to fully understand this repoHere's the transcript and the understanding.md Markdown file it created, which I've now added to the GitHub repo. I found the explanation of ONNX particularly enlightening:
ONNX (Open Neural Network Exchange) is a portable, framework-neutral file format for neural networks. An
.onnxfile is essentially two things bundled together:-
A computation graph — a directed graph of nodes, where each node is an operator (
Conv,MatMul,Add,Einsum,Softmax,Gather,Resize, …) wired together by named tensors flowing between them. This is the "recipe" for the forward pass. - The weights — the learned parameter tensors (the convolution kernels, the embedding table, etc.), stored as initializers in that same graph.
Crucially, ONNX describes what to compute, abstractly, without saying how or on what hardware. The operator set is versioned by an opset number (this repo uses opset 18), which pins down exactly which operators exist and what their semantics are.
It turns out PyTorch has built in mechanisms for exporting to ONNX, as seen here in export_onnx.py:
torch.onnx.export( dec, (lat,), dec_path, opset_version=args.opset, input_names=["latent"], output_names=["image"], dynamic_axes={"latent": {0: "B"}, "image": {0: "B"}}, )
Claude also included a handy glossary and an only-slightly-broken ASCII-art diagram showing how the model pipeline fits together.
You are only seeing the long-form articles from my blog. Subscribe to /atom/everything/ to get all of my posts, or take a look at my other subscription options.
-
🔗 tomasz-tomczyk/crit v0.16.5 release
What's Changed
Hotfix for v0.16.4: on finishing a review round, we now again include the full comment payload from
review.json(includingdom_anchor, replies, and other fields). The finish modal copy prompt is also clearer for agents, with an explicitcrit comments --jsonpath and a single next-command line when changes are requested.Fixes
- fix: include full comment fields in finish output and improve copy prompt (#683) by @tomasz-tomczyk in #683
Full Changelog :
v0.16.4...v0.16.5 -
🔗 r/reverseengineering how reverse engineering a h3c inode vpn caused a security nightmare rss
submitted by /u/ExtensionHeart4715
[link] [comments] -
🔗 r/reverseengineering VAXD - 1.60 rss
submitted by /u/Bicurico
[link] [comments] -
🔗 r/reverseengineering CAN bus reverse engineering with AI [Claude Code] rss
submitted by /u/csselectronics
[link] [comments] -
🔗 @HexRaysSA@infosec.exchange IDA 9.4 pre-release teasers continue. mastodon
IDA 9.4 pre-release teasers continue.
This week: a dramatically improved Dyld Shared Cache workflow. No more loading the entire DSC just to keep cross-references intact. IDA 9.4 brings on-demand loading, fluid navigation, new specialized widgets, and a clean API usable by both humans and agents.
👉 https://hex-rays.com/blog/ida-9.4-apple-dyld-shared-cache-workflow- improvements
-
🔗 Confessions of a Code Addict How Big Is a Physical Address? rss
This is a short aside in our virtual memory series. We have talked extensively about the size of a virtual address and the virtual address space, but we haven't yet looked at the size of a physical address.
As you might expect, physical addresses on x86-64 are smaller than 64 bits. But they are also not necessarily the same size as virtual addresses. In this video, we look at how big physical addresses are, how you can check this on your own machine, and why different processors may expose different physical address sizes.
If you haven't read the virtual memory article, do check it out. You can also download the ebook as a PDF from the link below.
-
🔗 tomasz-tomczyk/crit v0.16.4 release
What's Changed
- feat: add configurable open command (#671) by @ekisu in #671 - Thank you!
- feat: add crit comments command to list unresolved review comments (#677) by @tomasz-tomczyk in #677 - Thank you @pstibrany for suggesting!
- feat: structured comments in crit finish stdout (#678) by @tomasz-tomczyk in #678 - Thank you @ekisu for suggesting!
- fix: keep default-branch review visible after agent commits (#681) by @tomasz-tomczyk in #681
- fix: finish button ignores hidden unresolved comments on commit switch (#679) by @tomasz-tomczyk in #679
Internal refactors
- chore(deps): bump actions/checkout from 6 to 7 (#672) by @dependabot[bot] in #672
New Contributors
Full Changelog :
v0.16.3...v0.16.4 -
🔗 backnotprop/plannotator v0.21.1 release
Follow @plannotator on X for updates
Missed recent releases? Release | Highlights
---|---
v0.21.0 | Direct document editing in annotate mode, live git-status file tree, in-app agent terminal, open files in external apps, HTML renders as HTML
v0.20.3 | Annotations no longer lost when clicking away, off-screen indicator for open comments
v0.20.2 | Pierre CodeView all-files review, large-PR pipeline and instant-open checkout, unified agent engine selection, Pi programmatic plan mode
v0.20.1 | Pi extension install hotfix (pinned@pierre/diffsafter a broken upstream release)
v0.20.0 | Multi-repo workspace reviews, semantic diff overview, UI 2.0 themes and plan look chooser, leaner single-source skill install
v0.19.27 | Kiro CLI integration, Glimpse native window, annotate-last message picker
v0.19.26 | Amp plugin production fixes, Mermaid rendering fix, Settings flicker fix, update notification toast and shimmer
v0.19.24 | Amp integration, configurable data directory, Auto Mode permission option, Pi plan approval fix
v0.19.23 | Droid integration, Windows Pi AI fix, quieter update indicator
v0.19.22 | Safari copy fix in plan viewer, CLAUDE_CONFIG_DIR support for session logs
v0.19.21 | Ask AI in plan review and annotate mode, shared AI runtime, origin-aware provider defaults
What's New in v0.21.1
A single-fix patch release. Annotating the last assistant message could open a blank page in any conversation with more than one response. This release fixes that.
Annotate-Last No Longer Blanks on Multi-Message Sessions
Running
/plannotator-last(orplannotator last) opened a blank page whenever the session had two or more recent assistant messages. With zero or one message it worked, so it looked intermittent, but it was deterministic: the failure tracked message count. It was most visible on the Pi extension, where multi-turn sessions are the norm.The cause was a React rendering bug. When the annotate UI assembled the feedback payload during render, it reached a helper that wrote component state as a side effect. Because that state was a freshly built map on each pass, React never settled, hit its re-render limit, and threw before drawing anything, leaving an empty page. The fix makes that render-path helper a pure read of the same data, so the work that belongs at submit time no longer fires during render. The exported feedback is unchanged; the page renders.
PR #950 closing #949, reported, diagnosed, and fixed by @emmaneugene.
Install / Update
macOS / Linux:
curl -fsSL https://plannotator.ai/install.sh | bashWindows:
irm https://plannotator.ai/install.ps1 | iexExtra skills (compound, setup-goal, visual-explainer), opt-in:
npx skills add backnotprop/plannotator/apps/skills/extraClaude Code Plugin: Run
/pluginin Claude Code, find plannotator , and click "Update now".OpenCode: Clear cache and restart:
rm -rf ~/.bun/install/cache/@plannotatorThen in
opencode.json:{ "plugin": ["@plannotator/opencode@latest"] }Pi: Install or update the extension:
pi install npm:@plannotator/pi-extensionDroid: Install via the plugin marketplace:
droid plugin marketplace add backnotprop/plannotator droid plugin install plannotator@plannotatorAmp: Install the CLI first, then copy the plugin:
mkdir -p ~/.config/amp/plugins curl -fsSL https://raw.githubusercontent.com/backnotprop/plannotator/main/apps/amp-plugin/plannotator.ts \ -o ~/.config/amp/plugins/plannotator.tsKiro CLI: The installer auto-detects Kiro and installs skills automatically. After installing the CLI, launch with:
kiro-cli chat --agent plannotatorUpgrading from before v0.20.0? Read the v0.20.0 release notes first; that release changed how skills install.
What's Changed
- fix(editor): stop setState-during-render loop in multi-message annotate by @backnotprop in #950
Community
Thanks to @emmaneugene, who reported the blank-page regression in #949, traced it to the exact render-path state write, and supplied the fix that this release ships.
Full Changelog :
v0.21.0...v0.21.1 -
🔗 earendil-works/pi v0.79.10 release
New Features
- Extension compaction event context - Extension
session_before_compactandsession_compactevents now includereasonandwillRetry, so extensions can distinguish manual/compact, threshold auto-compaction, and overflow retry flows. See session_before_compact / session_compact and Custom Summarization via Extensions. - Safer update flow -
pi updateinstalls the exact checked Pi version, and update notices show the changelog URL, making upgrades more predictable. See Install and Manage.
Added
- Added
reasonandwillRetrymetadata to extensionsession_before_compactandsession_compactevents so extensions can distinguish manual, threshold, and overflow compaction flows (#5962 by @PizzaMarinara).
Fixed
- Fixed the
findtool to respect nested git repository boundaries when parent.gitignorerules ignore the nested repo (#5960). - Fixed the usage docs slash command table to include
/trustand/import(#5959). - Fixed inherited OpenAI-compatible streaming to preserve encrypted
reasoning_detailsthat arrive before matching tool call deltas (#5114). - Fixed broken TUI documentation links to the plan-mode extension example (#5957).
- Fixed transient extension UI and session-start messages emitted during session replacement or reload so they remain visible, and kept reload input blocked until reload completes (#5943).
- Fixed the plan-mode example to preserve active custom tools, skip the action prompt when no plan is found, and queue refinement/execution follow-ups correctly from
agent_end(#5940). - Fixed
pi updateto install the exact version returned by the Pi update check, make--forcereinstall that checked version, fail instead of falling back to an unversioned reinstall when no version is available, and report both the old and updated versions. - Fixed update notifications to display the actual changelog URL as the hyperlink text.
- Extension compaction event context - Extension
-
🔗 r/reverseengineering AirPlay 2 realtime audio sender, the encrypted RAOP/RTSP path reconstructed and documented rss
submitted by /u/Dangerous-Section567
[link] [comments] -
🔗 r/reverseengineering /r/ReverseEngineering's Weekly Questions Thread rss
To reduce the amount of noise from questions, we have disabled self-posts in favor of a unified questions thread every week. Feel free to ask any question about reverse engineering here. If your question is about how to use a specific tool, or is specific to some particular target, you will have better luck on the Reverse Engineering StackExchange. See also /r/AskReverseEngineering.
submitted by /u/AutoModerator
[link] [comments] -
🔗 r/reverseengineering HexWalk 2.1.0 Hex analyzer new release, new light theme, export analysis to csv, works both on Windows, Linux and MacOs, give it a try! rss
submitted by /u/gcarmix1
[link] [comments] -
🔗 HexRaysSA/plugin-repository commits sync repo: +1 plugin, +1 release rss
sync repo: +1 plugin, +1 release ## New plugins - [oh-my-dump](https://github.com/nevergiveupcpp/oh-my-dump) (1.0.15) -
🔗 r/reverseengineering JavaScript Obfuscator rss
submitted by /u/cascade_sparse
[link] [comments]
-
- June 21, 2026
-
🔗 IDA Plugin Updates IDA Plugin Updates on 2026-06-21 rss
IDA Plugin Updates on 2026-06-21
New Releases:
Activity:
- atelier
- f9876a21: fix(web-fetch): allow loopback and nonstandard ports
- 743aea4a: feat(search): honest-empty verdicts to stop retrieval spirals
- 2b39647e: chore: bump landing submodule (checkout, license flow, device manager)
- af644844: feat(licensing): self-service device management (CLI + web magic-link)
- 0f4a13b9: docs(contributing): add CLA and CLA-assistant workflow
- e58f7269: feat(bench): sandbox egress guard and harbor agent updates
- e2047308: feat(licensing): device-bound leases with auto-refresh and 3-device l…
- 7d8108fb: refactor(doc-sync): slim agent-context generator and unify managed ma…
- 4ade68fa: refactor(mcp): consolidate code-intel tools into explore
- d295eaa4: chore(license): relicense under FSL-1.1-ALv2
- disrobe
- 0926b0b3: bump py recovery to 93.45% (5874/6286); update svgs
- d36baae4: guard loop detection against else-arm misidentification and fix fused…
- ef7dc095: regenerate recovery/card/verification svgs for py 93.05 bump
- a95df9d6: recover dropped else branch when a guarded try is followed by a plain…
- 5c2e53a0: add missing semicolon in nir-lift python emitter (clippy semicolon_if…
- bd3334ff: extend arg-rest oracle with boa-graded leading-param shift cases (fun…
- ca8543ea: remove stray UnaryOperator import from arg_rest.rs (unused after walk…
- a38f4a96: rescue a try block preceded by a conditional guard into a guarded-try…
- c68e5967: extend bracket_to_dot/literal_logic/template_literal walkers to cover…
- e68b733f: pin the per-method javac recompile floor to 122/131 now that the reus…
- e716daac: harden the go buildinfo pointer-form decode with explicit ptr-size bo…
- d85546f2: split a local slot that holds a reference value and a primitive value…
- 933f28f5: split a local slot that holds a reference value and a primitive value…
- 18c7c78f: recover go modulename from the buildinfo blob, decode 32-bit abi type…
- 9b8bc0e1: undo bundler export aliasing: rename a local binding to the name in `…
- 2044ce4d: declare a local object when it holds both a constructed value and a c…
- 959d7bf3: undo bundler import aliasing: restore the developer name from `import…
- 15608126: recover the babel _createForOfIteratorHelper loop to for…of, runnin…
- 65738ad2: apply rustfmt across all crates that diverged from fmt canonical
- 6ca94bf1: apply rustfmt to disrobe-nir and disrobe-query
- doki-ida
- bd337e40: Update the theme now automatically crawl
- NexusRE-MCP
- 4825ef77: fix: Phase 0-2 architectural overhaul — runtime crashes, cache/sessio…
- oh-my-dump
- PseudoForge
- db3109ea: docs: update README for temp provenance reporting
- a75c6953: test: cover address taken temp provenance blockers
- b503a16e: feat: strengthen temp base provenance reporting
- 9c14c12f: feat: add cleanup integrity QA gate
- 726eddad: feat: add dense structural hints
- 743a7dc1: fix: clear stale llm fallback artifacts
- 9c0eb6e2: fix: suppress risky unassigned llm renames
- b1788466: fix: preserve multiline wrapped statements
- 56b8ab95: feat: track indexed callback corpus evidence
- 1ce6015e: feat: score indexed callback layout evidence
- 3156ab47: feat: report indexed callback layout evidence
- 8e5f5c84: feat: emit opaque target replay eas
- 1c88af25: feat: group opaque merge targets
- 074099d9: feat: report opaque merge call targets
- 1729cc84: feat: classify parameter merge provenance
- cde94c07: feat: expose merge provenance classes
- eb014955: feat: report allocation null dominance metrics
- c06ad448: feat: report call result equivalence metrics
- e35146a8: feat: report temporary merge provenance metrics
- 6b1339eb: fix: mask offset derefs in provenance comments
- server_info
- 2d206d80: init
- atelier
-
🔗 Simon Willison sqlite-utils 4.0rc1 adds migrations and nested transactions rss
sqlite-utils is my combined Python library and CLI tool for working with SQLite databases. It provides an extensive set of higher-level operations on top of Python's default sqlite3 package, including support for complex table transformations, automatic table creation from JSON data and a whole lot more.
I released sqlite-utils 4.0rc1, the first release candidate for sqlite-utils v4. The major version bump indicates some (minor) backwards incompatible changes, so I'm interested in having people try this out before I commit to a stable release.
New feature: migrations
There are two significant new features in this RC compared to the previous 4.0 alphas.
The first is support for database migrations. This isn't a completely new implementation - it's a slightly modified port of the sqlite-migrate package I released a few years ago. I think that package has proved itself over time, so I'm now ready to bundle it with
sqlite-utilsdirectly.Here's what a set of migrations in a
migrations.pyfile looks like:from sqlite_utils import Database, Migrations migrations = Migrations("creatures") @migrations() def create_table(db): db["creatures"].create( {"id": int, "name": str, "species": str}, pk="id", ) @migrations() def add_weight(db): db["creatures"].add_column("weight", float)
This defines a set of two migrations, one creating the
creaturestable and another adding a column to it.You can then run those migrations either using Python:
db = Database("creatures.db") migrations.apply(db)
Or with the command-line
migratecommand:sqlite-utils migrate creatures.db migrations.py
The system is deliberately small: it doesn't provide reverse migrations, so any mistakes you make should be fixed by deploying a fresh migration to undo them.
Its predecessor has been used by LLM and various other projects for several years, so I'm confident that the design is stable and works well.
The new migrations feature is documented here.
New feature: db.atomic() transactions
This feature is a lot less exercised than migrations, so it deserves more attention from testers.
Previously,
sqlite-utilsmostly left transaction management up to its users, via awith db.conn:construct that reused thesqlite3mechanism directly.SQLite supports nested transactions in the form of savepoints, so I wanted an abstraction that could make those as easy to use as possible.
I borrowed the terminology "atomic" from Django and Peewee. Here's what the new API looks like:
with db.atomic(): db.table("dogs").insert({"id": 1, "name": "Cleo"}, pk="id") try: with db.atomic(): db.table("dogs").insert({"id": 2, "name": "Pancakes"}) raise ValueError("skip this one") except ValueError: pass db.table("dogs").insert({"id": 3, "name": "Marnie"})
More details in the documentation.
Backwards incompatible changes
The backwards incompatible changes in v4 were described in the alpha release notes. For 4.0a0:
- Upsert operations now use SQLite's
INSERT ... ON CONFLICT SETsyntax on all SQLite versions later than 3.23.1. This is a very slight breaking change for apps that depend on the previousINSERT OR IGNOREfollowed byUPDATEbehavior. (#652) - Python library users can opt-in to the previous implementation by passing
use_old_upsert=Trueto theDatabase()constructor, see Alternative upserts using INSERT OR IGNORE. - Dropped support for Python 3.8, added support for Python 3.13. (#646)
-
sqlite-utils tuiis now provided by the sqlite-utils-tui plugin. (#648) - Test suite now also runs against SQLite 3.23.1, the last version (from 2018-04-10) before the new
INSERT ... ON CONFLICT SETsyntax was added. (#654)
And for 4.0a1:
-
Breaking change: The
db.table(table_name)method now only works with tables. To access a SQL view usedb.view(view_name)instead. (#657) - The
table.insert_all()andtable.upsert_all()methods can now accept an iterator of lists or tuples as an alternative to dictionaries. The first item should be a list/tuple of column names. See Inserting data from a list or tuple iterator for details. (#672) -
Breaking change: The default floating point column type has been changed from
FLOATtoREAL, which is the correct SQLite type for floating point values. This affects auto-detected columns when inserting data. (#645) - Now uses
pyproject.tomlin place ofsetup.pyfor packaging. (#675) - Tables in the Python API now do a much better job of remembering the primary key and other schema details from when they were first created. (#655)
-
Breaking change: The
table.convert()andsqlite-utils convertmechanisms no longer skip values that evaluate toFalse. Previously the--skip-falseoption was needed, this has been removed. (#542) -
Breaking change: Tables created by this library now wrap table and column names in
"double-quotes"in the schema. Previously they would use[square-braces]. (#677) - The
--functionsCLI argument now accepts a path to a Python file in addition to accepting a string full of Python code. It can also now be specified multiple times. (#659) -
Breaking change: Type detection is now the default behavior for the
insertandupsertCLI commands when importing CSV or TSV data. Previously all columns were treated asTEXTunless the--detect-typesflag was passed. Use the new--no-detect-typesflag to restore the old behavior. TheSQLITE_UTILS_DETECT_TYPESenvironment variable has been removed. (#679)
Try it out
You can install the new RC like this:
pip install sqlite-utils==4.0rc1
Or try the CLI version directly with
uvxlike this:uvx --with sqlite-utils==4.0rc1 sqlite-utils --help
Come chat with us about it in the sqlite-utils Discord channel, or file any bugs in GitHub Issues.
You are only seeing the long-form articles from my blog. Subscribe to /atom/everything/ to get all of my posts, or take a look at my other subscription options.
- Upsert operations now use SQLite's
-
🔗 r/reverseengineering Scrutari - forensic statistical analyzer for opaque firmware blobs rss
submitted by /u/XVilka
[link] [comments] -
🔗 Confessions of a Code Addict What Does a Page Table Entry Actually Store? rss
This is the 5th video in our virtual memory series. In the previous video, we learned about the page table and how the hardware performs a page table walk to do address translation. But merely finding the physical frame address in the page table is not sufficient for the hardware to do a memory access. It also needs to do certain additional checks to make sure that the access is valid. For example, it has to ensure that the page table mapping itself is valid (e.g., the page might have been swapped). Similarly, when doing a memory write, the hardware has to ensure that the page(s) are writable.
All these checks are done during address translation by looking up additional metadata stored against the page table entry. In this video, we cover what all these metadata bits needed by the hardware are, what their purpose is, and how they are stored in the page table.
In the next video, we will discuss demand paging. In the meantime, if you haven't yet read the VM article, please do. And if you prefer reading it offline, get the ebook from the link below.
-
🔗 r/reverseengineering Reverse once, run forever: designing client-side defenses that assume the attacker has already read every line rss
submitted by /u/TrustSig
[link] [comments] -
🔗 r/reverseengineering I reverse engineered Windows Copilot into a free OpenAI compatible API (GPT-4o, no API key, no billing) rss
submitted by /u/whatisonearth
[link] [comments] -
🔗 r/reverseengineering NØW — Word-Based Shellcode Encoder rss
submitted by /u/ObligationLucky842
[link] [comments] -
🔗 r/reverseengineering Note20 ABL Odin out-of-bounds read rss
submitted by /u/Greenlinkx
[link] [comments] -
🔗 Mitchell Hashimoto Pledging Another $400,000 to the Zig Software Foundation rss
(empty) -
🔗 Jamie Brandon 0060: SF rss
(empty)
-
- June 20, 2026
-
🔗 IDA Plugin Updates IDA Plugin Updates on 2026-06-20 rss
IDA Plugin Updates on 2026-06-20
Activity:
- atelier
- 5e89dc0c: fix(skills): make perf-review stack-agnostic for distribution
- 410417f0: fix(skills): make perf-review stack-agnostic for distribution
- dae584cd: feat(skills): add perf-review skill with measured perf gates
- 62b12a81: test: realign stale assertions with the shipped agent/tool surface
- f6cd080c: docs(readme): rocket emoji on hero + reformat benchmark tables
- disrobe
- eb995072: skip the grpc serve e2e on windows where the spawned server deadlocks…
- 1318b65c: skip the gnu-bash base64 oracles on macos where bsd base64 differs
- 0d6362a4: use is_ok_and in the bash base64 probe to satisfy clippy
- 96e8427c: skip bash base64 oracle when gnu base64 (-w0/-d) is unavailable (eg m…
- d26bc23b: skip the stack-string oracle when gcc emits no elf object (eg macos m…
- c7b54edf: skip the gcc resolver oracle when the object is not elf (eg macos mac…
- 70f3f91f: revert py-decompile chain to source-terminal output to preserve mcp/c…
- 4843e690: make py-decompile chain manifest deterministic for portable goldens a…
- 841eca10: js: emit the recovered source as a chain child so auto surfaces it un…
- 1d7a2a12: fix electron e2e to read recovered js from the mixed fan-out output l…
- a4a44ba1: js: bless the chain golden for the mixed fan-out sidecar output
- 8da0b285: lua chain pass emits recovered source + lua.manifest.json sidecar so …
- a4196d29: js-deob chain: emit detection/recovery/pipeline sidecars as auto chil…
- 038502a6: py.deob auto/chain emits the peelresult manifest sidecar
- 906a9ebb: py.decompile chain emits source + manifest sidecar for auto parity
- 0f4263b3: py.disasm chain emits the structured .dis.json sidecar child so auto …
- 691b23f6: native packer-unpack chain pass emits dedicated sidecars for auto parity
- ddd4c944: make external-tool round-trip gates skip when the tool is unavailable…
- 8766e68f: fmt the hand-edited capabilities rules and frisk tests
- 6bbaaa90: recover swift protocol requirements from __swift5_protos descriptors
- rikugan
- ToCode
- 3eba5881: Merge pull request #9 from buzzer-re/dev/improves
- 942b3320: Gate IDA cache rebuild behind -purge-cache
- 31b19fb5: Recover from a broken cached IDA database
- bad636ac: Rebuild stale IDA caches and relativize absolute source paths
- 4f3502f2: Merge pull request #8 from buzzer-re/dev/improves
- 3e932ecd: Ignore missing elftools imports in mypy
- 11073372: Import DWARF line/file info in the IDA loader
- dba1445a: Fix IDA source/type recovery to use the real IDA APIs
- atelier
-
🔗 earendil-works/pi v0.79.9 release
New Features
- Chat-template thinking compatibility - OpenAI-compatible custom providers can map Pi thinking levels into
chat_template_kwargs, enabling vLLM/Hugging Face chat-template models such as DeepSeek to use provider-native thinking controls. See Custom Provider API Types and OpenAI Compatibility. - GLM-5.2 provider improvements - GLM-5.2 now has corrected Fireworks OpenAI-compatible routing and OpenRouter
xhighthinking support, improving/modelbehavior and high-effort reasoning for GLM-5.2 users. See Model Options.
Added
- Added inherited configurable
chat-templatethinking support for OpenAI-compatible providers that usechat_template_kwargs, such as DeepSeek models behind vLLM (#5673).
Fixed
- Fixed inherited Fireworks GLM-5.2 metadata to use the OpenAI-compatible Chat Completions endpoint with
reasoning_effortsupport (#5923). - Fixed same-directory session switches to reuse imported extension modules while preserving fresh extension instances and lifecycle events (#5905).
- Fixed deep session branches taking quadratic time to build context or branch paths (#5909).
- Fixed inherited OpenRouter GLM-5.2 metadata to expose
xhighreasoning and send OpenRouter's nativexhigheffort (#5770). - Fixed inherited Markdown streaming code fence rendering so partial closing fences no longer make code blocks shrink or flicker while content streams (#5846 by @xl0).
- Fixed fuzzy
editmatches to preserve untouched line blocks instead of rewriting the whole file through normalized content (#5899). - Fixed bash commands through legacy WSL
bash.exeto pass scripts over stdin so shell variables expand in the target bash (#5893). - Fixed
/modelto hide GitHub Copilot models that are unavailable to the authenticated account (#5897). - Fixed
/modelselector search to rank exact provider-prefixed matches before proxy-provider model ID matches (#5892).
- Chat-template thinking compatibility - OpenAI-compatible custom providers can map Pi thinking levels into
-
🔗 r/reverseengineering Built a full Android RE suite that runs on-device — Radare2 CFG graphs, 8 Java decompilers (CFR, Procyon, Krakatau…), Flutter & il2cpp support rss
submitted by /u/SkullDecay-
[link] [comments] -
🔗 r/reverseengineering Reverse engineered Pixel 7 Tensor G2 access rss
submitted by /u/qbnasasn
[link] [comments] -
🔗 r/reverseengineering DOS Game "F-15 Strike Eagle II" reversing project needs DOS test pilots rss
submitted by /u/lowlevelmahn
[link] [comments] -
🔗 sacha chua :: living an awesome life La semaine du 7 au 14 juin rss
lundi 8
Pendant la routine matinale, j'ai remarqué qu'une des nouvelles boucles d'oreilles de ma fille était perdue. J'ai cherché dans son lit et sa chambre, mais je ne l'ai pas trouvée. Pendant que j'expliquais à mon mari, je l'ai remarquée sur le sol dans la cuisine. J'ai réussi ! C'est ma vie de pie.
L'école a eu un remplaçant, donc je l'ai prévenue de son absence. Ma fille et moi sommes assises dehors et nous avons fait quelques devoirs comme un jeu de Donjons et Dragons. Par exemple, il fallait que ma fille récapitule le chapitre de son devoir de lecture pendant qu'elle était à l'envers à cause d'un piège que mon roublard a dû désamorcer. Je pense que c'est plus facile de faire ses devoirs si ma fille peut bouger.
Nous avons alterné entre les devoirs et notre propre partie de Donjons et Dragons. Nous avons essayé le module gratuit qui s'appelle Dagger Danger. L'interface virtuelle de D&D Beyond était un peu difficile à apprendre. Nous avons préféré utiliser les dés physiques.
J'ai emmené ma fille à son cours de gymnastique. Son entraîneur habituel n'était pas là. Il y avait une remplaçante qui s'appelle Ashley. Ma fille n'aime pas s'habituer aux nouvelles personnes, mais à mon soulagement, elle a continué la séance.
Après le cours, elle a eu très chaud. Je l'ai emmenée au glacier Scoops pour essayer leur crème glacée. Ils ont une ristourne pour les enfants entre 15h00 et 17h00. Elle a commandé la crème glacée à la fraise dans un cornet sucré et elle a payé cash.
Ma fille a cueilli des petites fraises dans la cour avant. Il y avait quelques fraises que les insectes ont mangées, mais il y avait certaines fraises qui étaient meilleures que celles que nous achetons au supermarché. J'étais surprise que les écureuils ne les aient pas mangées.
Mon mari a grillé du poulet au miel et à l'ail. C'était délicieux.
Avant de faire la vaisselle, ma fille et moi avons eu un très long câlin. Je lui ai dit que je ne veux pas la lâcher en premier, donc j'ai pensé qu'elle avait aussi décidé de ne pas lâcher d'abord. C'était une merveilleuse impasse. Finalement, elle m'a dit qu'elle voulait m'aider avec la vaisselle, donc nous l'avons faite.
J'ai acheté les livres de mathématiques Beast Academy 4, et nous les avons reçus tard le soir. Ma fille est restée debout tard parce qu'elle a voulu lire tous les livres.
mardi 9
Ma fille a décidé de sécher les cours aujourd'hui. Elle était fatiguée et grincheuse parce que j'étais préoccupée par de la paperasse.
Mon mari, ma fille et moi avons livré des dons pour la Bike Brigade.
J'ai travaillé sur la paperasse pour les assurances-vie. Je les ai revérifiées contre les confirmations parce que les bénéficiaires irrévocables diffèrent.
Sur Tileman Reworked dans Stardew Valley : enfin, j'ai accédé au forgeron et au musée le 11 de l'hiver de la quatrième année. J'ai amélioré mon arrosoir. J'ai aussi pêché à la fête du calmar pendant que j'écoutais l'enregistrement de mon rendez-vous avec mon tuteur.
mercredi 10
J'ai vérifié que j'avais reçu le virement bancaire, et j'ai envoyé quelques questions sur les assurances-vie.
J'ai travaillé comme consultante. J'ai mis à jour mon logiciel pour copier des données parce que j'avais remarqué que les nouvelles données avaient changé l'identifiant. Je dois le vérifier après l'avoir exécuté. J'ai aussi ajouté la capacité d'afficher des vidéos à la galerie.
Pendant que ma fille jouait avec ses amies à la pataugeoire, j'ai corrigé la transcription de la conversation entre ma sœur et notre cousine, une de ses meilleures amies depuis l'enfance. Elles sont toujours allées à la même école, et quand sa famille était notre voisine, ma sœur allait souvent chez elles. La reconnaissance vocale a fait plusieurs erreurs parce que ma sœur et notre cousine avaient utilisé deux langues. La majorité des histoires étaient en anglais, mais il y avait aussi des phrases en filipino. WhisperX peut transcrire quelques phrases philippines, mais d'autres phrases étaient traduites en anglais. Ce n'était pas grave. J'ai écouté l'enregistrement sur mon smartphone et j'ai écrit mes corrections sur ma tablette, et j'ai passé un bon moment.
J'ai perdu momentanément mon Apple Pencil. Je l'ai trouvé dans mon gilet à nombreuses poches.
jeudi 11
J'ai parlé d'Emacs avec Prot.
J'ai eu rendez-vous chez le dentiste pour faire des plombages. Je lui ai expliqué ma situation avec les limites du petit compte gestion-santé et mon budget pour les frais dentaires par année. C'est définitivement une grande dépense cette année, mais je pense que c'est mieux que je trouve un dentiste qui prend les bonnes précautions contre le COVID.
Ma fille et moi avons lu ensemble deux livres illustrés sur le TDAH et sur l'autisme.
Ma fille n'a pas déjeuné parce qu'elle a eu besoin de la salle de bain.
vendredi 12
Ma fille n'a pas voulu participer à l'école parce qu'il y avait encore un remplaçant. Nous avons fait deux exercices de ses devoirs ensemble.
J'ai eu rendez-vous avec mon tuteur pour m'entraîner à la conversation. Nous avons parlé du temps, des voyages et des réseaux.
J'ai transféré l'argent que j'avais reçu de ma sœur aînée à l'autre banque.
Ma fille et moi sommes allées à la banque. J'ai payé l'amende et elle a déposé de l'argent.
Elle a perdu une de ses boucles d'oreilles. Heureusement, je l'ai retrouvée près de sa chaise. La prochaine fois, je dois les resserrer en les nettoyant.
samedi 13
J'ai travaillé sur la paperasse de l'assurance-vie.
Sur Donjons et Dragons, nous avons gardé les prisonniers.
Sur Stardew, j'ai amélioré ma pioche et ma hache.
Mon mari, ma fille et moi avons regardé le film Donjons et Dragons parce que maintenant ma fille peut comprendre toutes les blagues. Nous nous sommes amusés. #fr
dimanche 14
Mon tuteur français habituel va prendre des vacances la semaine prochaine. Je pense à trouver un autre tuteur pour explorer d'autres méthodes d'apprentissage. Si l'un d'eux me plaît, je peux replanifier mes rendez-vous pour faire une alternance. C'est mieux que je m'entraîne à écouter des personnes variées.
J'ai mis à jour le mot de passe de ma fille avec l'aide de son enseignant.
J'ai travaillé comme consultante. J'ai corrigé des erreurs après la mise à jour du système.
Malgré la pluie, ma fille et moi sommes allées au supermarché à pied pour acheter des œufs et d'autres aliments.
Lizzy a commencé un nouveau personnage qui s'appelle Celeste, une magicienne tieffeline. J'ai créé une guerrière qui s'appelle Olga pour l'accompagner. Nous avons collecté les chèvres perdues de Cornflower.
Ma fille était déçue parce que nous n'avions pas d'œufs pour préparer une grande omelette comme d'habitude. Elle s'est assise contre sa porte.
You can e-mail me at sacha@sachachua.com.
-
🔗 r/reverseengineering GitHub - lautarovculic/ioscpy: A macOS CLI that mirrors and controls a jailbroken iPhone over USB. rss
submitted by /u/lvculic
[link] [comments] -
🔗 r/reverseengineering sterrasec/apk-interceptor: Android deeplink, Intent, and WebView bridge assessment helper rss
submitted by /u/tkmru
[link] [comments] -
🔗 Stephen Diehl Prism: An Impure Functional Language With Typed Effects rss
Prism: An Impure Functional Language With Typed Effects
This is going to be a very nerdy post so bear with me. Here is a function. Read it the way you would read any other function, and then tell me its type.
fn fib(n) = var a := 0 var b := 1 repeat(n) fn let t = a + b a := b b := t aThat is a mutable loop. There is a
var, there is assignment, there is a temporary so the swap does not eat itself. It is, line for line, thefibyou would write in Python after deciding that recursion was a young person's game.Its type is
Int -> Int, it is functional but in place. There is no effect type even though the function has effects, because the effects are not observable from outside the function. As far as anyone calling it is concerned, this function is pure. It mutates two variables in place and then, before the door closes behind it, sweeps up the evidence and leaves no fingerprints. And the compiler does it all for you. It's the code you would write in Python with types you get from OCaml and no monads.This is Prism, a proof of concept functional compiler I've been working on for the last three years, built around modeling effects with modern types inspired by the intellectual lineage of OCaml 5, Haskell and Koka. The big idea of the last five or six years of functional programming is that effects are real, effects are fine, and the interesting question is not how to avoid them but how to put them in the type system and then optimize them until they cost nothing.
Effects Are Interfaces
The one idea you need is the algebraic effect handler. An effect declares operations; a handler gives them meaning. Here is a producer that yields a sequence and has no idea who is listening:
effect Gen { ctl yield(Int) : Unit } fn produce(n) : !{Gen} Unit = if n == 0 then () else yield(n) produce(n - 1)The
!{Gen}in the type is the function confessing, in writing, that it performs theyieldoperation and someone upstream had better deal with it. Now we hand the same producer to two different handlers:fn total(n) = handle produce(n) with yield(v, k) => v + k(()) return r => 0 fn count(n) = handle produce(n) with yield(v, k) => 1 + k(()) return r => 0The
kis the continuation, the rest of the computation, reified as an ordinary value you can hold in your hand.totalresumes it and adds;countresumes it and counts. A handler can ignorekentirely (that is an exception), call it once (that is state, or a generator), or call it many times. This last one is the move that makes algebraic effects more than sugar. Here a handler finds Pythagorean triples by resuming the same continuation once per candidate, which is to say it explores a whole search tree using nothing but straight-line code and a handler that says "yes, and also try the other branch":effect Amb { ctl choose(Int) : Int, ctl reject(Unit) : Int } fn triple(n) : !{Amb} Int = let a = choose(n) let b = choose(n) let c = choose(n) if a > 0 && b > 0 && a <= b && a * a + b * b == c * c then a * 10000 + b * 100 + c else reject(()) fn solutions(n) = handle triple(n) with choose(m, k) => flatten(map(\(i) -> k(i), range(0, m))) reject(u, k) => Nil return r => Cons(r, Nil) fn main() = let sols = solutions(14) println(length(sols)) println(sum(sols))choose(n)offers a value in0..n-1andreject()prunes a dead branch, and because the handler resumeskonce for every candidate,triplereads like a function that just picks three numbers.If you have used OCaml 5 this will feel familiar, except OCaml keeps its effects out of the types, so you find out about an unhandled one at runtime, in production, on a Friday. If you have used Haskell this will also feel familiar, except in Haskell you would be assembling a monad transformer stack, lifting each operation through every layer by hand, and explaining to a junior colleague that a monad is just a monoid in the category of endofunctors, what's the problem. Prism's effects are row polymorphic. They union structurally across calls. There is nothing to stack and nothing to lift, because there is no tower, only a set.
One Trick, Five Ways
Once effects are first class, a remarkable number of ideas from the last thirty years of language design turn out to be unified under the same mechanism:
Exceptions are a handler that throws away the continuation. A clause marked
final ctldiscardsk, so its body's value becomes the handler's result and the rest of the computation is simply abandoned. NoResultthreading, no?confetti up the call stack, just direct-style code that stops:fn safe_grade(n) = handle grade(n) with final ctl abort(msg) => concat("invalid: ", msg) return r => rAnd because an exception is just a label in the effect row, you get extensible exceptions for free. Each distinct failure is its own operation, so the row in a function's type spells out exactly which exceptions can escape it, the way
!{Gen}spells out that it yields. There is no rootExceptionclass to inherit from and no hierarchy to edit; a new exception is just a new label. They union structurally across calls, so a function that canabortand a function that cantimeoutcompose into one whose row carries both. Handling one of them discharges its label and leaves the rest in the row, which means partial recovery is something the type system tracks rather than something you promise in a comment:effect Abort { ctl abort(String) : Unit } effect Timeout { ctl timeout(Int) : Unit } -- fetch's row spells out both failures it can raise fn fetch(id) : !{Abort, Timeout} String = if id < 0 then abort("bad id") if id > 99 then timeout(id) "ok" -- discharge Timeout with a fallback; Abort still escapes fn with_default(id) : !{Abort} String = handle fetch(id) with final ctl timeout(_) => "cached" return r => rThe handler peels
Timeoutoff, sowith_defaultis left carrying exactly!{Abort}, no more and no less. Java's checked exceptions wanted to be this and could not, because they were welded to the class hierarchy instead of being an open, structural set.Generators and streams are a producer that performs
emit, transformers that catch it and re-emit, and a consumer that folds. A pipeline is handlers nested around one producer, which means there is no intermediate list, by construction:srange(1, n).smap(square).skeep(even).stake(5).ssum()Stopping early, the
stake(5), is just a handler dropping a continuation once it has what it needs. Cancellation produces garbage, and that garbage is reclaimed at a statically known point with no collector involved, which is the good part we will come back to. The stream library was inspired by Haskell's pipes and conduit.Lenses are not a library anymore they are language-integrated. They are record-update paths plus the memory model. Given three nested record types, one path expression reaches arbitrarily deep and sets several fields at once:
type Vec2 = Vec2 { x: Int, y: Int } type Player = Player { pos: Vec2, hp: Int } type Game = Game { player: Player, score: Int } deriving (Lens) let g2 = { g | player.pos.x = 30, player.hp = 95, score = 110 }That rebuilds the spine of the nested record, and when the value is uniquely owned, each rebuild reuses the cell it just took apart, so a functional update compiles to a pointer write. No optic types are allocated, nothing is composed at runtime, the path is just addresses. The entire optics ecosystem, the van Laarhoven encoding, the profunctor zoo, the operators that look like a cat walked across the keyboard, all of it collapses here into one syntax rule and a memory discipline. And when you genuinely need to pass an accessor around,
deriving (Lens)hands youscore_ofandwith_scoreas ordinary functions:let g3 = with_score(g2, 200) -- score_of(g3) == 200They are boring functions, which is the highest compliment in functional programming!
Mutable state is the
varfrom the opening. Eachvardesugars to a private effect withgetandsetoperations, discharged by a handler installed at the end of its block. The state never escapes, an analysis rejects any closure that would try to smuggle it out, and the enclosing function keeps its empty row. This is the loop you would write in Python with the signature you would want in Haskell and none of theStatemonad plumbing in between.Failure is the most fun, because it is functional logic programming sneaking in through the effect row. An anonymous
Faileffect makes "this expression might not produce a value" a thing the type system already knows how to talk about.fail()performs it,guard(cond)performs it when a check is false, and the consumers read like a wish list:let port = cfg.at_map("port") ?? cfg.at_map("https") ?? 443 let off = customer?.tier?.discount ?? 0 let bill = [item for item in sof(cart), if prices.at_map(item) > 4]??falls back when the left side fails.?.chains through options and short-circuits. The comprehension guard prunes elements that fail instead of crashing. And becausevaris itself just handler sugar, an entire block can be transactional:transactsnapshots every live variable, runs the body in a failure context, and rolls everything back if it fails, so an overdrawn account behaves as if the purchase never happened.transact balance := balance - price guard(balance >= 0) balance else 0Five features. One underlying mechanism, viewed through a five dimensional prism. And that lovely idea is the namesake!
Modern Types
So far we haven't seen a lot of type signatures which is the point, most of the time you can write down quasi-Python-looking code and inference is decidable and predictable via the usual complete-and-easy Dunfield-Krishnaswami algorithm. You annotate only where you genuinely cross into higher-rank territory, which is rare, and the algorithm meets you exactly at that boundary. A function can demand a genuinely polymorphic argument, declared with a
forallon the binder, and then use it at several types in one body:fn pick(g : forall a. (a) -> a) : Int = if g(true) then g(10) else g(20) fn main() = println(pick(\(x) -> x))gis forced to be polymorphic, sopickmay apply it to aBooland anIntin the same breath, which is whymaincan only hand it the identity function and not, say, a number. A Damas-Milner core would have unifiedawithBoolon the first call and rejected the second; here theforallsurvives into the argument.Ad-hoc polymorphism is type classes, but Lean-flavored: instances are named values, not anonymous magic resolved by global coherence. You write
given Ord(a)to ask for a dictionary, and you can name exactly which one you want at the call site when more than one is in scope:instance ordDesc : Ord(Int) { fn cmp(x, y) = int_cmp(y, x) } sort_by_ord(xs) -- the default Ord(Int) sort_by_ord[ordDesc](xs) -- this one, reversedThe instances you do not care about, you do not write. One
derivingclause off a type declaration synthesizes the boring ones, and the field accessors too:type Vec2 = Vec2 { x: Int, y: Int } deriving (Eq, Ord, Show, Lens) with_x(v, 7) -- a derived setter, and FBIP-reused when v is uniqueClasses also feed pattern matching, which is the part that tends to make PL people sit up. A
patterncan name a class method as its view, and then that one pattern deconstructs every type with an instance, dispatched by dictionary exactly like a method call:pattern First(n) for Peek = view peek fn head_or(x : c, d : Int) : Int given Peek(c) = match x of First(n) => n _ => dFirst(n)matches aBox, aRange, or anything else that isPeek, andhead_oris generic over all of them at once. The pattern is as polymorphic as the function around it.Some ad-hoc polymorphism does not even need a class.
showis type-directed: the compiler infers the static type of its argument and synthesizes a structural printer from the real constructor names, recursing into fields, with no instance to write and no runtime type dispatch (the printer is monomorphized from the static type, so it never reads a runtime tag to decide how to print):show(42) -- "42" show([1, 2, 3]) -- "[1, 2, 3]" show((7, false)) -- "(7, false)" show(Node(Leaf, 1, Leaf)) -- "Node(Leaf, 1, Leaf)"And the third axis is the one the rest of the post is really about: the same row variable that makes effects composable makes them polymorphic. Here is a higher-order function that calls its argument twice and adds the results. The argument's effect row is a variable
e, which is to saytwicedoes not care whatfdoes, only that it returns anInt:fn twice(f : (Unit) -> Int ! {| e}) = f(()) + f(())The
{| e}is "this row, and whatever else." Each call site unifiesewith the actual row of the thunk it passes, and that is the whole trick: one definition serves a pure argument, an effectful one, and an effectful one of a completely different effect, with no overloads and no wrapping.fn pure_use() = twice() fn(u) 21Here
eunifies with the empty row{}. No effect is performed, so no handler is needed, and the result is just42. Now force the sametwiceto carry an effect through:fn tick_use() = handle twice(\(u) -> tick(())) with tick(u, k) => \(n) -> k(n)(n + 1) return r => \(n) -> rThe thunk performs
Tick, soeunifies with{Tick}, and the surroundinghandledischarges exactly that one label. Swap in a thunk that performsSayinstead andebecomes{Say};twiceitself never changed. A handler only ever names the labels it wants, andequietly carries everything else along, which is what lets these functions compose without a transformer stack to thread them through.The same machinery, three times: one definition, abstracted over types, over dictionaries, and over effects.
Zero-cost Abstractions
At this point the cynical hater reader (hi Hacker News!) is thinking the thing most armchair peanut gallery types think about elegant abstractions, which is: sure, but what does it actually cost. Algebraic effects have a reputation. The textbook implementation reifies your whole computation into a free monad, a tree of "here is an operation, and here is a function for what to do with its result," and then an interpreter walks that tree allocating a small cell at every single operation. It is beautiful and it is a heap allocation per
yield.Prism does not do that on the path that matters. The fast path is evidence passing in the Koka lineage, with my own deviations noted where they matter: instead of reifying the computation and reaching for the handler, it carries the active handler clause to each operation site as an ordinary parameter. A
do opbecomes a direct call. So we don't need a tree, or interpreter loop, or cell. The only allocation is one closure per handler when you install it, which is O(handlers), not O(operations). You can performyielda billion times under a handler that was allocated exactly once.The free monad is still in the building, because some patterns genuinely need it (a computation that escapes into a data structure, a truly multishot resumption, a masked handler). But it is the fallback, not the default, and the compiler proves which one you are in.
Now point this at the stream pipeline from earlier:
for n in srange(1, 11).smap(square).skeep(even) do print(n)An interprocedural flow analysis works out, across function boundaries, exactly which effect evidence each producer and transformer needs, and threads it through. The whole chain lowers to a single loop with zero intermediate lists and zero per-operation cells. This is essentially Haskell-like stream fusion, but the thing Haskell achieves with rewrite rules that fire only if you hold your imports correctly and the wind is blowing precisely the right direction while Mercury is in retrograde, and what Rust achieves by monomorphizing iterator adapters into one another at the type level. Here it falls out of the effect compiler, because once
emitis a direct call to a known clause, inlining the clause into the producer is just inlining.The cleanup, meanwhile, is the good part promised earlier, and it is not a garbage collector. Prism uses Perceus reference counting: every heap cell is freed at a statically known point, deterministically, with no pauses and no tracing. And then the clever idea is that we have frame-limited reuse, where a cell you just deconstructed in a pattern match gets handed straight back to the constructor on the other side of the arrow, so
mapover a uniquely owned list mutates the list in place while remaining, on paper, a pure function returning a new one. The lens update compiling to a pointer write is this same machinery. So is the loop infibnot allocating. Purity and mutation turn out to be the same thing viewed from different ends of an ownership analysis, which is either a deep idea about the nature of computation or quite banal, and I'm not quite sure which.The Runtime & LLVM Backend
If Perceus reference counting sounds familiar, it should: it is the same memory discipline Lean 4 runs on, and for the same reason, a dependently typed proof assistant cannot afford GC pauses any more than a game loop can. Lean compiles to C and links a little runtime that does the counting. Prism does the structurally identical thing, except it emits LLVM IR (through
inkwell) and, for the same program, a text MLIR module, and then hands the result toclangto link against one hand-written C runtime,prism_rt.c, about a thousand lines.That runtime is deliberately tiny. A heap cell is four-plus words,
{ refcount, tag, arity, fields... }, and the whole file is the allocator, therc_inc/rc_decpair that the compiler sprinkles through the code, the in-place reuse allocator that the FBIP pass targets, and the bignum and string primitives. There is no collector thread, no card table, no safepoints, nothing that runs when your code is not running. The reference counting is not a service the runtime provides at runtime; it is code the compiler already wrote into your program, and the C file is just where the four or five operations it calls happen to live.It links against plain libc
mallocby default (there is an opt-inmimallocknob for benchmarking, but the whole thesis is "do not allocate," and a fast allocator only hides the allocations you failed to eliminate). A live-cell oracle asserts the heap balances to zero at exit, so "garbage free" is a property the test suite checks rather than a thing the README asserts.Runs In Browser through WASM
The same interpreter that serves as the differential oracle compiles to wasm, so there is a playground where you can type Prism and run it without installing anything. It runs the program in a worker, and the buttons next to it will dump the inferred type signatures (effect rows and all) and the lowered core IR, so you can watch a
varloop or a stream pipeline turn into the thing it actually compiles to. Every example in this post is in the dropdown. Poke at the effect rows; they are the whole point. The full source, the Lean model, and the C runtime all live in the prism repository on GitHub.Nerd Stuff
For completeness, if you have read this far you have clearly made some very questionable life choices (hi fellow traveller!) so here's the PL nerd stuff:
- Type inference is the usual SOTA bidirectional and higher-rank, the complete-and-easy Dunfield-Krishnaswami algorithm, so rank-N polymorphism works without you annotating your way out of it.
- Typeclasses with Lean-style named instances (
instance ordInt : Ord(Int)), explicit override (sort_by_ord[ordRev](xs)), andderiving (Eq, Ord, Show). - A mostly OCaml-ish-shaped surface syntax : layout blocks, dot chains (
xs.over(f).keep(g).sum()),withsugar for continuation-passing code, string interpolation, effect row aliases. - Deep recursion runs in constant stack, both natively (tail calls, and tail recursion modulo a constructor) and in the interpreter (it is essentially a more advanced version of the old CEK machine, so nothing in your program can blow the host stack).
And, for the genuinely afflicted, the full intellectual lineage. Obviously standing on the work of many FP giants, but the ones that are most directly inspired Prism's design are:
- The core IR is call-by-push-value (Levy, Call-by-Push-Value: A Functional/Imperative Synthesis, 2001), whose split between values and computations is the thing that makes both the effect lowering and the reference counting analysis clean rather than heroic.
- The fast effect path is evidence passing (Xie and Leijen, Generalized Evidence Passing for Effect Handlers, ICFP 2021; Effect Handlers, Evidently, ICFP 2020), the same compilation strategy Koka uses, with the free-monad encoding (Swierstra's Data Types a la Carte, and Kiselyov's extensible effects) kept only as the fallback.
- The memory model is Perceus (Reinking, Xie, de Moura, Leijen, Perceus: Garbage Free Reference Counting with Reuse, PLDI 2021) plus frame-limited reuse (Lorenzen and Leijen, Reference Counting with Frame-Limited Reuse, ICFP 2021) and fully-in-place programming (Lorenzen, Leijen, Swierstra, FP^2: Fully in-Place Functional Programming, ICFP 2023), which is also what turns the lens sugar into pointer writes.
- The effect rows are row polymorphism with scoped labels (Wand 1987; Leijen, Extensible Records with Scoped Labels, 2005; Type Directed Compilation of Row-Typed Algebraic Effects, POPL 2017), and handlers themselves are Plotkin and Pretnar (Handlers of Algebraic Effects, ESOP 2009) by way of Eff and Koka.
- Pattern matching compiles to decision trees with a usefulness matrix for exhaustiveness (Maranget, Compiling Pattern Matching to Good Decision Trees, 2008; Warnings for Pattern Matching, 2007), and the
patternforms are view patterns crossed with GHC's Pattern Synonyms (2016). - The failure layer is the Verse calculus (Augustsson, Peyton Jones, Steele, Sweeney, et al., The Verse Calculus, ICFP 2023), recovered entirely from
final ctlhandlers with no new core. - A subset of the core is mirrored in a Lean 4 model (
models/Prism.lean) with a machine-checked determinism theorem, and the three backends are held byte-identical by treating the interpreter as a differential oracle.
The thesis, if a toy project gets to have one, is that "purely functional" was always a slightly defensive name for a good idea. The good idea is that effects should be visible, typed, and composable. You do not need to forbid them to get that; you need to track them. And once you are tracking them honestly, the compiler has enough information to make them free, which is the part the purity narrative never promised you, because it was too busy not making eye contact with the IO monad.
This compiler is essentially my love letter to the old 2010-20s era of functional programming which was a much simpler and happier time (or maybe that's just the rose-tinted glasses of youth in the ZIRP era). This compiler is a toy, it is for all intents and purposes useless except maybe as kind of a piece of art from a bygone time before the Transformer-era of programming. The world we're headed to doesn't really have a place for this kind of thing anymore. We're increasingly headed to a world in which maximally probabilistic hilariously-uninteresting Typescript increasingly runs most of the world as we babysit these token extruders while the economy and markets become increasingly automated by agents. While this makes me kind of sad, it doesn't mean we still can't build this kind of thing just for fun and that's what I did here. Maybe the future of functional programming languages is increasingly niche-but-economically-irrelevant and becomes more like a hobby pursuit for the few of us who still love it for the intellectual beauty of the underlying ideas. So I built it anyways, and it runs, and it was a blast to build. And just maybe that's enough, in this brave new world of software.
-
