- â
- â
- September 28, 2026
-
đ MetaBrainz GSoC 2026: GraphQL Server For Musicbrainz rss
Hi everyone, I'm Sreehari, also known online as owlpharoah (op3kay on Matrix). I'm a second year student at IIIT Jabalpur. This summer I worked on the foundations of a GraphQL server in Rust that sits over the MusicBrainz PostgreSQL database, under the guidance of @bitmap and @jadedblueeyes.
The Setting
MusicBrainz already has an XML/JSON API, but getting related data out of it means chaining together inc parameters, and browsing support differs from one entity type to the next. Looking up five artists at once isn't really possible either.
GraphQL fixes most of this by letting a client ask for exactly the fields and relationships it wants in one query. It also lets the server check how expensive a query is before running it, instead of finding out after the database has already taken the hit.
Before writing the proposal, I built a rough prototype covering Artist, Release Group, Release, and Recording, mainly to see how things would click together. Two problems showed up: N+1 queries on relationship fields, and the fact that depth limiting alone doesn't catch a shallow query that's still expensive. Both became core parts of the proposal.
The Plan

The proposal scoped the project to six entity types: Artist, Release Group, Release, Recording, Label, and Area. Each would be queryable by MBID, with the usual relationships between them, plus aliases, tags, genres, and ratings across the board.
A few decisions were made initially:
- DataLoaders would follow a two tier split. One loader maps an entity's internal id to the hydrated entity itself and gets reused everywhere that entity shows up. A separate, thinner loader maps a parent id to a list of child ids.
- Fields that need a loader call would live behind ComplexObject, so they only run when a client actually asks for them.
- Pagination would use keyset pagination instead of offset pagination, since offset pagination gets slow and inconsistent on large tables that change often.
- Query safety would come from depth limiting plus a complexity weight on every resolver.
key goals
- A working GraphQL server covering the six entity types
- Schema level depth limiting and query cost analysis
- A performance baseline from load testing.
The Result
DataLoader infrastructure is in place across all six entities, following the two tier split. Loaders exist for tags, ratings, artist credit, genres, annotations, aliases, ISNI and IPI identifiers, and MBID to internal id resolution. Hydration loaders are shared across every relationship that points at a given entity instead of duplicated per relationship.

MBID redirect handling lives inside each loader's load function. When a primary table lookup misses, the unresolved MBIDs get batch queried against the matching *_gid_redirect table, and any hits get merged into the result map. A resolver further up never has to know a redirect happened.
Keyset pagination runs across the paginated fields using ROW_NUMBER() OVER (PARTITION BY parent_id ORDER BY child_id) in a single batched query, so one query can apply a per parent limit across a whole batch of parents. The cursor ended up as a plain integer rather than the opaque string from the proposal.

Query complexity weights reflect actual database work: a scalar field costs its default, a single hop DataLoader field costs a flat amount, a paginated one to many field scales with the requested page size, and multi hop fields carry a multiplier on top.
Integration tests cover all six entities.
Week 7's load testing with k6 turned up two findings worth fixing. There was an N+1 on the isrc field, fixed with a dedicated RecordingIsrcLoader, and a gap in the complexity limiter where a pathological query executed instead of getting rejected outright.
On the infrastructure side, CI runs pre commit hooks and no longer has dead code warnings, and documentation is wired up through Magidoc in Docker compose.
What I Learned
The two tier loader split sounded simple on paper, but it's saved me a lot of time in practice. Adding a new relationship is now mostly copying hydration logic that already works, instead of writing it fresh.
Fixture data needs to be checked against the database it's running against, not assumed to be stable. musicbrainz-docker's sample dumps import non- deterministic subsets of the data, so an MBID that resolves cleanly on my machine can point at something else, or nothing, on someone else's. That cost me a few confused debugging sessions before I figured out what was going on.
What's Next
- Criterion benchmarking, to compare the current per row queries on ComplexObject fields like Release.date against a batched loader variant, and to compare the two hop ArtistCredit and Tags loader patterns against a single joined loader.
- Moka caching is still an open evaluation.
- Extended entity coverage beyond the original six.
Conclusion
This was an awesome summer and i enjoyed thinking about and wiring up the API schema and the Postgresql database, fixing bugs, and everything in between. It was really satisfying watching the two tier loader pattern click into place once and then just work for every relationship added after it.
Thanks to my mentors for the guidance, especially on the DataLoader architecture, which I wouldn't have landed on alone. And thanks to the wider MetaBrainz community for the space to build this in. It's been a pretty nice summer of query plans, a lot of Rust, and debugging, and I'd do it again.
-
đ Szymon Kaliski Q3 2026 rss
Hi!
We had a great summer. Having both indoor and outdoor pools within walking distance from our home meant a lot of swimming, mostly in the kids' pool with our (now two year old!) daughter.
It's been another year where we spend the hottest months at home. The summer is pretty nice here, and most days already feel almost like vacation. We travel during the grey and cold months instead, since we don't have to worry about the school year yet.
My time was split purely between work and family, so there's not much to report, other than publishing Play with Putty at Google Labs â a research prototype exploring collaborative vibe-coding:
There's a lot more about this that's not covered by the video and I hope to share more soon. For now, you can sign up for the waitlist.
Worth Checking Out
What I've been reading lately:
- Something Incredibly Wonderful Happens â history of Frank Oppenheimer and his Exploratorium, great read, highly recommended
- Experiences In Visual Thinking â awesome walk through what it means to think visually, and exercises to get better at it
- Thirty Years that Shook Physics â a historical view of development of quantum theory
- The Character of Physical Law â a couple of very dense lectures from Richard Feynman about generic principles of (and about) physical laws
- Thinking About Mathematics â only just started this one but seems great so far, overview of philosophy of mathematics
On the web:
- Aphex Twin logo generator
- some cool prototypes around using LLMs for design explorations
- Andrew Blinn has been on a roll: 1, 2, 3, and a short talk
- Zach Lieberman on 10 years of daily sketching
- some great posts from Jamie about AI, curious to see what he'll get up to at Anthropic
-
- September 27, 2026
-
đ Simon Willison 2026 in LLMs (so far) rss
On Friday I gave the closing keynote at the WeAreDevelopers World Congress North America in San Jose. I tied together the key trends from the past year into a chronological exploration of everything that happened in 2026. The video is on YouTube; here are my annotated slides and notes to accompany the talk.
And as an annotated presentation:
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.
-
đ smol-machines/smolvm smolvm v1.19.3 release
What's Changed
- Bump the Nix package to 1.19.2 by @BinSquare in #1429
- Run embedded commands on their own connection, with cancel and per-command user by @BinSquare in #1430
- Write less on pause and resume by @LoganGrasby in #1433
- Refuse machine exec --user on a bare VM instead of running as root by @BinSquare in #1432
- Carry the host's proxy and trusted certificates into a machine on request by @BinSquare in #1431
- Fix Windows machines failing to boot in v1.19.0 by @BinSquare in #1439
- Bump the workspace to 1.19.3 by @BinSquare in #1440
Full Changelog :
v1.19.2...v1.19.3 -
đ r/Harrogate Anyone else think this sculpture is a bit weird? rss
| Thoughts! submitted by /u/FrontRaspberry5060
[link] [comments]
---|--- -
đ crosspoint-reader/crosspoint-reader 1.6.5 release
Summary
Library view
Recent Books has grown into a powerful way to browse your entire collection on your SD card. Sort by recently added, title, or author, or just search for the book you want.
TTF support
Devices with external RAM (X4Pro, Sticky, X4C, PaperMono) can now use TrueType (.ttf) fonts directly â no need to convert them to CrossPoint's
.cpfontformat first. Drop your fonts into/fonts/or/.fonts/on the SD card, restart the reader, and pick them from the text settings. If a family has separate regular, bold , italic , and bold-italic files, put them together in one family folder..cpfontfonts still work everywhere, including on devices without external RAM. OpenType (.otf) fonts are also supported, though compatibility may vary.Cover Grid home theme
The new Cover Grid theme displays your recent books as a grid of covers on the home screen. This theme is not available on x3 and the original x4.
More control over reading
New word and character spacing controls let you adjust how tightly text sits on the page, alongside the existing line-spacing settings. Footnote navigation now highlights references directly on the reading page and and bookmarks can finally be renamed.
Touch controls and navigation
Touch devices gain configurable tap and swipe gestures for turning pages. Headers now have back buttons, and fixes improve scrolling in reader lists and settings. X4pro also gain configurable shortcuts for Home button.
X4 Classic support
The ESP32-S3-based X4 Classic is now officially supported. Get one now
Sleep Screens
Sleep screens across all devices now show drastically higher quality grays. On the X4 and certain Pro display variants they push the quality to the max. Transparent pngs got a nice quality bump as well.
Clock & About
You can now view the clock on the home screen! In addition, the old utc offset picker is gone and is replaced with auto DST and a list of cities to pick from when setting the time. This setting has moved to the more obvious system settings. You'll also find a new About option in the system settings as well which is useful for identifying display controllers during debugging.
Everything else
Files can be renamed straight from the file browser. Portuguese gets hyphenation support, Korean text justification stretches spaces between words rather than within them, and the keyboard gains an Arabic layout.
KOSync now sends more precise EPUB reading positions. Fixes also address EPUB lists, hidden content, chapter-position displays, and end-of-book navigation.
This release reduces font and EPUB memory pressure, fixes USB drive disconnection, improves web file-transfer safety, and prevents EPUBs that fail to render their first page from reopening automatically after wake.
What's Changed
- chore: update pioarduino to 55.03.311 by @serialx in #3397
- fix: Fixes KOSync memory checks and reduces memory pressure by @itsthisjustin in #3412
- fix: pack font manifest catalog into one arena by @fain182 in #3398
- docs(issue forms): Correct links to scope/roadmap by @cassidyjames in #3433
- fix(reader): synchronize end-of-book menu selection by @Daviex in #3418
- fix: render NFD Hangul filenames from macOS transfers by @serialx in #3036
- fix: reader's menu book chapter current position by @unnamedd in #3437
- fix: stabilize X3 EPUB anti-aliasing by @uxjulia in #3439
- fix(input): wake the idle poll on raw button contact so short presses register by @Techneaux in #3463
- feat: HTTP serve static with Cache-Control and ETag headers by @shirok1 in #2560
- chore: add direct download links for PR artifacts by @Uri-Tauber in #3389
- fix(webserver): normalize every user-supplied path and escape file names in the files page by @s0lness in #3353
- chore: Consolidates grayscale capability checks and enables absolute grayscale for supported screens by @itsthisjustin in #3478
- fix: don't display elements with hidden HTML attribute by @jjharpham in #3390
- fix(KOSync): compare mapped KOReader sync positions by @WhoTheHeck in #3111
- fix: update OTA to recognize the new format by @Uri-Tauber in #3493
- fix(debugging_monitor): if PSRAM is logged, add subplot by @olifre in #3490
- fix(KOSync): preserve precise KOSync upload progress positions by @WhoTheHeck in #3174
- refactor: reduce EPUB heap fragmentation with unique ownership by @serialx in #3518
- fix: reduce font-cache heap fragmentation by @serialx in #3521
- fix: release font caches before EPUB chapter layout by @serialx in #3527
- chore: add x4 Classic to CI pipelines by @Uri-Tauber in #3532
- docs: make roadmap easier to scan by @fain182 in #3517
- fix: number ordered lists and fix list container indents by @jan-xyz in #3500
- fix: dropped presses while a list repaints by @Techneaux in #3534
- perf: batch SdFat's SPI transfers on ESP32 by @osakanataro in #3501
- fix: USB OTG not disconnected when you unplug the cable by @itsthisjustin in #3538
- feat: Library view by @Uri-Tauber in #3366
- fix: Skip bw rendering on sleep images & fix white as transparent for sleep covers by @itsthisjustin in #3541
- chore: Resolve CI Node.js warning by @Uri-Tauber in #3545
- fix: Let OPDS catalog screens auto-sleep by @Uri-Tauber in #3547
- feat: Rename bookmarks by @Uri-Tauber in #3549
- fix: handle progressive JPEGs with separated component scans by @lpla in #2925
- fix: warp lists in library by @Uri-Tauber in #3556
- feat: add home button shortcuts by @uxjulia in #3516
- feat: Add AboutActivity for device information display by @itsthisjustin in #3563
- perf: keep settings list at its initial allocation by @serialx in #3569
- feat: Replace UTC offset with timezone and DST settings by @itsthisjustin in #3562
- perf: reduce image related heap fragmentation by @lpla in #2332
- fix: preserve frontlight state across silent restarts by @uxjulia in #3575
- feat(keyboard): add an Arabic layout by @rxmmah in #3430
- chore: Update license by @Uri-Tauber in #3578
- Swaps IMU direction on Sticky for proper gyro tilts by @itsthisjustin in #3583
- fix: clear ligature views when releasing SD font caches by @serialx in #3581
- fix: restore space widths on partial cache misses by @serialx in #3585
- fix(ci): pin pioarduino core inside the platform penv by @serialx in #3594
- feat: rename files by @Uri-Tauber in #3572
- fix: Re-Enable toolbar reader menu on button-only devices by @itsthisjustin in #3603
- fix: stop materializing every File Browser row (FUI list rowProvider) by @itsthisjustin in #3600
- fix: tear down WiFi when leaving the Settings network screen by @uxjulia in #3613
- feat: choose which UI languages are compiled in by @eszter007 in #3618
- chore: Update German translation by @BlueDragon333 in #3621
- fix: restore NFC composition in File Browser rows by @serialx in #3630
- feat: add word and character spacing controls by @serialx in #3528
- feat: refresh library by @Uri-Tauber in #3608
- fix: unblock X4 Pro frontlight double-click when short power press = Sleep, add Home key option by @naydichev in #3089
- feat: Adds Cover Grid UI Theme for PSRAM capable devices by @itsthisjustin in #3657
- feat: Add CrossInk style swipe and tap controls by @itsthisjustin in #3586
- perf: reduce per-pixel overhead in font rendering by @serialx in #3633
- feat: Convert slider dialogs to popups over current page by @itsthisjustin in #3669
- fix(settings): fix list scrolling by @brianhuster in #3668
- fix: Split navigation modes for cover grid & add selected states by @itsthisjustin in #3670
- fix: stop duplicating identical interval tables across font styles by @eszter007 in #3616
- fix: wrap OTA completion power-on hint by @lpla in #3632
- feat: Add TrueType fonts on PSRAM boards by @itsthisjustin in #3646
- fix: Sticky env missing ram stats by @Uri-Tauber in #3683
- fix: Reader list not scrollable with touch by @itsthisjustin in #3693
- feat: add Portuguese hyphenation support by @type0labs-dev in #3643
- chore: address Python Sonar warnings by @lpla in #2515
- feat: better footnotes navigation by @Uri-Tauber in #3682
- fix: Switch tap zones for RTL books by @Uri-Tauber in #3709
- fix: justify Korean text by word spaces only by @serialx in #3700
- feat: add back buttons to the header and unify all theme headers by @itsthisjustin in #3689
- fix: Remove loading icon display during startup by @itsthisjustin in #3671
- fix: release SD-font caches on reader exit by @serialx in #3699
- perf: avoid main-loop contention during EPUB rendering by @serialx in #3652
- fix: Toolbar menu in landscape mode + TTF font sizes by @Uri-Tauber in #3748
- fix: don't reopen unreadable EPUB on wake by @SurayaAtouraya in #3724
- fix: Remove article stripping logic by @Uri-Tauber in #3749
- chore: Update translations by @Uri-Tauber in #3396
- fix: don't cut screenshot folder names mid-letter by @SurayaAtouraya in #3750
- fix: speed up long status titles by @Uri-Tauber in #3573
New Contributors
- @cassidyjames made their first contribution in #3433
- @Daviex made their first contribution in #3418
- @unnamedd made their first contribution in #3437
- @Techneaux made their first contribution in #3463
- @shirok1 made their first contribution in #2560
- @s0lness made their first contribution in #3353
- @jjharpham made their first contribution in #3390
- @olifre made their first contribution in #3490
- @osakanataro made their first contribution in #3501
- @eszter007 made their first contribution in #3618
- @naydichev made their first contribution in #3089
- @type0labs-dev made their first contribution in #3643
- @SurayaAtouraya made their first contribution in #3724
Full Changelog :
1.6.0...1.6.5
Downloads
crosspoint-1.6.5-papermono.bin
crosspoint-1.6.5-sticky.bin
crosspoint-1.6.5-x3-x4.bin
crosspoint-1.6.5-x4c.bin
crosspoint-1.6.5-x4pro.bin -
đ Confessions of a Code Addict How Copy-on-Write Works with Memory-Mapped Files rss
In the last video, we discussed demand paging. Now, let's move towards an even more interesting topic: copy-on-write (CoW). Just like demand paging, CoW is the kernel's internal mechanism with implications for the performance of user- space systems. It enables multiple processes to share data in RAM between them in read-only mode. The interesting bit is that the processes themselves are not aware of this sharing, as far as they are concerned they are executing as if they are the only ones working with that data. Of course, this works as long as the processes are reading the data. When one of them needs to do a write to this shared data, the kernel needs to make a copy before the write can happen, hence the name "copy-on-write "!
This is a very wide and deep topic, so I'm going to split into multiple videos. This first video goes deep inside the kernel to explain what CoW is, how the kernel implements it, and for that we will take the example of mmap to read and write files.
Following are some of the major sections in the video with the timestamps to help you navigate. Also, I recommend watching the video at higher speed to get a better experience.
-
00:00 -- Why CoW matters: memory use, page faults, and unpredictable latency in data-intensive applications.
-
(06:34) The page-table picture: how different processes can map the same physical frame.
-
(11:44) CoW in one diagram: share a page while reading; make a private copy when writing.
-
(15:06) Mapping a file with
mmap: the call's arguments, includingMAP_PRIVATE. -
(21:31) What
mmapcreates: a virtual address range and VMA, before the file page is maps into the process. -
(25:03) The first read: address translation, a page fault, and how the kernel resolves it.
-
(32:25) The page cache: where file data is held in RAM and why another process can reuse it.
-
(38:43) A second process maps the file: its own page fault leads to the same cached physical page.
-
(43:11) A private write: why writing to that shared file page would violate
MAP_PRIVATE. -
(45:51) Write protection and CoW: how a read-only PTE causes a write fault and the kernel gives the writer a private copy.
-
(49:07) What happens next: the second process can also get a copy; later writes to an already private page proceed without another CoW fault.
A minor correction note : At about 46 minutes, when I say mappings of page-cache pages are read-only, I mean the
MAP_PRIVATEmappings in this example. A writableMAP_SHAREDmapping can modify a cached file page, which is later written back to the file.If you are new to this series, it is based on my ebook called "Virtual Memory from First Principles". It is available to read for free online and also available to purchase from Gumroad (PDF/Epub) and Amazon (Kindle edition).
And, if you want to watch the previous videos in this series, the following is what has been published so far:
-
-
đ Julia Evans Replacing the old battery on rechargeable bike lights rss
Hello! Recently I needed bike lights for my bike. And I remembered that I already had rechargeable bike lights that I bought ten years ago, that I hadn't tried in a long time. I tried to recharge them, but after fully charging them, they only worked for maybe 5 minutes before they turned off again.
I don't know much about electronics, but I've been curious about whether it's possible to fix old electronics for a long time, and this seemed like the perfect repair project because I might just need to replace the battery.
So I went to the local queer makerspace where I'm a member to use the soldering iron and try to do it! I don't know much about electronics and this post does not contain any safety advice because I don't know much about safety. I think it's nice to do projects in a community space where you can get help.
step 1: cut it open
The bike light felt like it was made of silicone, so I cut open the silicone in a haphazard way along something that vaguely looked like a seam.
I definitely ripped some silicone in the process and it was pretty messy but I got it open and found the circuit board.
I don't know the model number of the bike lights but there's a photo of them at the end of the post.
step 2: remove the screws
There were some screws attaching things together so I removed them so I could get the circuit board out.
Mostly I tried to remove as few screws as possible because I was worried about losing them or not being able to put them back after. I probably put the screws in a bag or something.
step 3: get the circuit board out
I took out the circuit board. Here's what it looked like:

You can see where the battery is attached, I think it's left of RI3 and above Q2.
Here's what the battery looked like:

step 4: desolder the battery
I'd never desoldered anything before, so I found the iFixit guide to desoldering and read it. Also I asked my friends Lee and Lauria for advice.
Here were the steps I ended up following based on the guide & the advice I got:
- Use a desoldering pump to remove most of the solder
- Once most of it is gone, kind of pull them apart to try to separate them
- Also try to avoid getting the battery too hot in the process by taking breaks to let it cool down. I'm not very good with a soldering iron so it took a while.
- The battery has an attachment that is welded to the top. For a while I thought I needed to remove this and it seemed impossible, but it turned out the replacement battery comes with that part so actually I was supposed to leave it alone.
step 5: identify the battery
In the picture of the battery in Step 3, you can see it says something like "3" and "LI???77". There's a piece of metal that I think is welded or something to the top of the battery. It seemed impossible and also maybe not smart to try to remove so I wasn't sure how to find out what an "LI????77" was or how to order another one.
I've been trying to avoid using LLMs (though I will not get into that because I am exhausted by LLM discourse and I'm sure you are too), but I really had no idea how to figure out what the battery was so I asked an LLM. It gave the response "LIR2477", which (when I looked it up) looked exactly the same as my battery so I figured that was plausible.
I would be interested to learn non-LLM ways to figure this out though. There must be a way. Lauria showed me how to use DigiKey's search which was very cool though DigiKey didn't have that part.
(edit: someone in the replies told me that this kind of coin cell battery is named according to its dimensions, and you can use plastic calipers to measure the dimensions of the battery. So I guess a non-LLM way would be to measure the battery with calipers and try to match it to something on the List of battery sizes Wikipedia page, though that page only mentions CR 2477 and not LIR 2477. It's a good example of what's fun for me about trying to avoid LLMs, this "List of battery sizes" page is super interesting and if I use an LLM I might never find it)
step 6: buy the battery
I went to AliExpress and ordered:
- 2 batteries (I had 2 bike lights and I wanted to fix them both)
- some silicone glue to glue things back together
I think the batteries were $3 each and the glue was $8.
step 7: solder the new batteries in and glue it back together
The parts took maybe 2 weeks to arive, and once they arrived, I went back to the makerspace and:
- soldered in the new batteries
- put the screws back in. The screws were very small and hard to hold, so at this point I dropped some screws on the ground and couldn't find them because they were too small. So I just used fewer screws and hoped for the best.
- used the glue to try to put everything back together.
- Make a somewhat halfhearted attempt to clamp the parts I was gluing together
Then after waiting some amount of time for the glue to dry I took it home and waited 24 hours for the glue to cure.
Also I took the old batteries to somewhere nearby that accepts old batteries.
it works!
The lights work! I have used them to bike at night! I still haven't needed to recharge them (and tragically I had to order a new Mini USB cable because I got rid of all my Mini USB cables, so I'm still waiting for that), so I still don't know for sure how long the lifetime of the new battery will be.
Here's what the light looks like after re-gluing. You can see that I didn't glue very carefully. It didn't really go back together that well but I'm hoping it'll be good enough.

I thought it was really cool that I was able to do this with extremely minimal electronics skills! It cost about $20 CAD to buy the parts, and (whether or not the repair holds up, I'll try to update this post in the future!), it was fun to try to repair something and learn something new.
-
- September 26, 2026
-
đ backnotprop/plannotator v0.27.21 release
Follow @plannotator on X for updates
Missed recent releases? Release | Highlights
---|---
v0.27.20 | Mistral Vibe support, annotate gets the full Options menu and Settings, jj Commits panel, long lines wrap in plan code blocks
v0.27.19 | Before/After image previews in code review, file comments as GitHub file threads, forge-correct#123links,/plannotator-lastfinds the right session
v0.27.18 | Model pickers from your installed Claude and Codex (Opus 5.5, Fable 5.1, GPT-6), unsent PR review comments survive new pushes
v0.27.17 | Diagram files open in the diagram viewer, OpenCode switches model with agent, idle review stops polling the git remote, Tree is the default review view
v0.27.16 | Themed diagrams on Mermaid 12, comment on any node or edge, patch-file review, embedded HTML documents render
v0.27.15 | Plannotator TUI and Herdr Annotate announcement, element context on pinpoints, HTML links open as linked documents, All files panel, Classic diff default
v0.27.14 | Pi plan progress survives compaction, Codex threads across rollout files, WSL browser setting, Mod+E edit mode
v0.27.13 | Open a review on a specific base (--base,--diff-type), symlink containment on /api/doc, CI flake fix, Amp decision relay
v0.27.12 | Unified decision control, token hover cards, local-vs-remote diff, approval notes
v0.27.11 | OpenCode server leak fix, durable local feedback archive, unknown-subcommand fix
v0.27.10 | Auto-viewed files on scroll, annotation undo/redo, OpenCode 2 slash commands restored, npm 12 agent terminal fix
v0.27.9 | WebMCP browser-agent tools, HTML refresh from disk, host seams, lazy renderers, Windows uninstall fixWhat's New in v0.27.21
A fix release built mostly from community reports. Remote and phone sessions load several times faster, code review can request changes on GitHub for real, model pickers show readable names and say where their list came from, and several OpenCode rough edges are gone. Seven pull requests, answering issues from four people.
Remote and phone sessions load several times faster
Every session sent the whole app to the browser as one uncompressed file, about 25 MB for plan review and 18 MB for code review, on a fresh port each time, so nothing could be cached. On a fast local connection that goes unnoticed. Over a slow tunnel, such as a phone reaching a Mac through Tailscale, it meant waiting up to two minutes before anything appeared.
Remote-mode and
--tailscalesessions now send the page compressed: brotli over--tailscale's HTTPS, gzip over plain http. Measured cold loads at 206 KB/s and 159 ms, the connection from the report:| Before |
--tailscale| Remote mode
---|---|---|---
Plan review | 118 s | 30 s | 34 s
Code review | 86 s | 21 s | 24 sLocal sessions send exactly the same bytes and headers as before, since compressing on the same machine gains nothing. The page is also about 1 MB smaller for everyone: KaTeX's math fonts no longer ship in two older formats that no supported browser loads. Splitting the app into separately loaded pieces, which measured around 5 to 7 seconds on the same link, is the next step and is tracked in the same issue.
(#1619, refs #1617, reported by @giladbarnea)
Request changes on GitHub pull requests
In a pull request review, Post comments, then⌠promised a choice between requesting changes and staying neutral, and Request changes⌠promised the same. Neither existed: every review posted as a plain comment. The submission dialog now offers Comment or Request changes, and a Request changes review posts as
REQUEST_CHANGESon GitHub, including reviews with file-level comment threads. On your own pull request the option is disabled with the reason, since GitHub refuses it. GitLab has no equivalent, so the option is disabled there and posting is unchanged.(#1613, closing #1611, reported by @RobertoArtiles)
Model pickers show real names and where the list came from
Claude Code 2.1.282 changed how it describes its models, and Plannotator started labeling specific Claude versions with their marketing tagline instead of their name: several rows read "Best for everyday, complex tasks" and could not be told apart. Every Claude picker now shows the model's name again, and older Claude Code versions keep working.
Each Claude and Codex model picker (code review agents, Code Tour, Guided Review, Ask AI) also shows where its list came from, such as "From your installed Codex 0.155.1", or says it is using the built-in list and suggests updating or signing in to the tool. The built-in list, used only when Plannotator cannot read your installed tool's models, now includes the GPT-6 models. Guided Review defaults to GPT-6 Luna for Codex when your Codex offers it, since guides generate faster on a lighter model; a model you already picked always wins.
If a newer model is missing from your picker, update the tool itself (
codex update, or update Claude Code) and restart the review. The picker shows whatever your installed tool reports.OpenCode fixes
- Feedback reaches the right agent.
/plannotator-lastand/plannotator-annotatefeedback was answered by OpenCode's default agent even when the annotated message came from a different one. Feedback now goes to the agent that wrote the message, as long as OpenCode still lists it; otherwise it is delivered exactly as before. (#1614, closing #1612, reported by @balaji-dutt) - Guided Review and review agents work with OpenCode v2. Plannotator started
opencode runwith a--dirflag that OpenCode v2 rejects. It now starts OpenCode in the review's folder and sets its working-directory environment to match, which works on both versions and also keeps OpenCode 1.x from reviewing the wrong checkout in PR, worktree and multi-repo reviews. (#1610, refs #1609, reported by @JakobHavtorn)
Additional Changes
- Selections elsewhere on the page survive. The document viewer cleared the whole page's text selection whenever it loaded or its content changed. It now only clears a selection inside itself. This mainly affected apps that embed the viewer next to other panels. (#1608)
Install / Update
macOS / Linux:
curl -fsSL https://plannotator.ai/install.sh | bashWindows:
irm https://plannotator.ai/install.ps1 | iexClaude Code Plugin: Run
/pluginin Claude Code, find plannotator , and click "Update now".Pi: Update
@plannotator/pi-extensionto 0.27.21 and restart Pi.OpenCode: Clear cache and restart:
rm -rf ~/.bun/install/cache/@plannotatorWhat's Changed
- fix(ui): vim selection reset only clears selections inside the viewer by @backnotprop in #1608
- fix(agents): drop --dir from opencode run (OpenCode v2 rejects it) by @backnotprop in #1610
- fix(review): make Request changes a real PR review event by @backnotprop in #1613
- fix(opencode): answer /plannotator-last feedback with the annotated message's agent by @backnotprop in #1614
- fix(core): name Claude models from displayName when the description is only a tagline by @backnotprop in #1615
- Model pickers: show where the list came from; refresh the Codex fallback by @backnotprop in #1616
- perf: compress the app page for remote and tailnet sessions; woff2-only KaTeX by @backnotprop in #1619
Community
This release is mostly answers to reports:
- @giladbarnea measured the two-minute phone load precisely, down to the link speed and what compression alone would save, in #1617
- @RobertoArtiles traced the missing request-changes choice through the source in #1611
- @balaji-dutt reported OpenCode feedback reaching the wrong agent, with an agent-by-turn table, in #1612
- @JakobHavtorn reported the OpenCode v2
--dirfailure in #1609
Full Changelog :
v0.27.20...v0.27.21 - Feedback reaches the right agent.
-
đ smol-machines/smolvm smolvm v1.19.2 release
What's Changed
- Bump the Nix package to 1.19.1 by @BinSquare in #1426
- Let a Landlock-confined VMM move files between directories of its read-write paths by @BinSquare in #1427
- Bump the workspace to 1.19.2 by @BinSquare in #1428
Full Changelog :
v1.19.1...v1.19.2 -
đ @HexRaysSA@infosec.exchange AI agents can now work in IDA, using the official IDA MCP server. đ§ mastodon
AI agents can now work in IDA, using the official IDA MCP server. đ§
IDA MCP is free and open source. It's model-agnostic, so you can use any capable LLM, running locally or in the cloud.
What's different:
â Code Mode: a small set of tools, with agents working through IDAPython. That uses ~20% fewer tokens than popular alternatives
â IDA Nexus: many agents and humans on the same IDBs, with changes showing instantly in the IDA GUI (and vice versa)
â Runs headless with idalib for large-scale pipelines, and fully local for air-gapped environments
â Works with IDA Pro, Home, Classroom and OEMOne-command installs for Claude Code, Codex, GitHub Copilot, Pi and oh-my-pi. Any stdio MCP client works too.
Install (requires uv):
uvx ida-hcli mcp installFull details đ
https://hex-rays.com/blog/hex-rays-ida-mcp-server -
đ r/Harrogate Best Lunch Deals rss
As the title says, what are the best restaurant lunch deals in Harrogate mid- week? Seen Pranzo 2 courses for £20 and Tannin Level 2 course for £23⌠appreciate any other suggestions!
submitted by /u/Various-Note-9396
[link] [comments] -
đ r/Harrogate Coldbath Road Reccomendations rss
Any insight?? The old man is coming up tomorrow at 11am what is there to do on coldbath?
Iâve only ever walked up is there anywhere for a coffee or lunch you would advise on
submitted by /u/FrontRaspberry5060
[link] [comments] -
đ Anton Zhiyanov Go concurrency distilled rss
This mini-book provides a brief overview of many concurrency topics in Go. Each topic comes with interactive examples â feel free to experiment with them by changing the code and clicking Run. There's also a PDF version with static examples.
This is a quick refresher on Go concurrency, not a beginner's guide. If you want to learn concurrency from the ground up with practical exercises, check out my other book â Gist of Go: Concurrency.
The book is AI-free.
Goroutines ⢠Channels ⢠Select ⢠Pipelines ⢠Time ⢠Context ⢠Wait groups ⢠Data races ⢠Race conditions ⢠Mutexes ⢠Semaphores ⢠Signaling ⢠Run once ⢠Object pool ⢠Atomics ⢠Testing ⢠Scheduling ⢠Diagnostics ⢠Final thoughts
# Goroutines The foundation of concurrency in Go is goroutines â functions started with the go keyword: func main() { var wg sync.WaitGroup wg.Add(2) go func() { defer wg.Done() fmt.Println("worker 1") }() go func() { defer wg.Done() fmt.Println("worker 2") }() wg.Wait() } worker 2 worker 1 The Go runtime juggles these goroutines and distributes them among operating system threads running on CPU cores. Compared to OS threads, goroutines are lightweight, so you can create hundreds or thousands of them. Goroutines are completely independent. The main function is also a goroutine, but it starts implicitly when the program starts. When main ends, other goroutines also shut down. We use a wait group (sync.WaitGroup) to wait for goroutines to finish in the example above. A wait group has a counter inside. Calling Add(n) increments it by n, while Done() decrements it by one. Wait() blocks the calling goroutine (in this case, main) until the counter reaches zero. This way, main waits for both workers to finish before it exits. WaitGroup.Go automatically increments the wait group counter, runs a function in a goroutine, and decrements the counter when it's done: func main() { var wg sync.WaitGroup wg.Go(func() { fmt.Println("worker 1") }) wg.Go(func() { fmt.Println("worker 2") }) wg.Wait() } worker 2 worker 1 # Channels Goroutines can pass values to each other through channels. A channel is like a window where one goroutine can throw something and another can catch it: func main() { messages := make(chan string) go func() { messages <- "ping" }() msg := <-messages fmt.Println(msg) } ping Sending a value through a channel is a synchronous operation. When the sending goroutine writes a value to the channel (ch <- val), it blocks and waits for someone to receive that value (<-ch). Only then does it continue. Output channel Returning an output channel from a function and filling it within an internal goroutine is a common pattern in Go. This allows the caller to receive values through the channel while the owning function retains control of it: func generate(start, stop int) chan int { out := make(chan int) go func() { for i := start; i < stop; i++ { out <- i } }() return out } Closing a channel To signal readers that all data has been sent, the writer goroutine closes the channel with close(): func generate(start, stop int) chan int { out := make(chan int) go func() { defer close(out) for i := start; i < stop; i++ { out <- i } }() return out } The reader checks the channel's status with a second value ("comma OK") when reading: func main() { in := generate(5, 10) for { num, ok := <-in if !ok { break } fmt.Print(num, " ") } } 5 6 7 8 9 While the channel is open, the reader receives the next value and a true status. If the channel is closed, the reader gets a zero value and a false status. A channel can only be closed once. Closing it again or writing to a closed channel causes a panic. The only reason to close a channel is to signal to its readers that all data has been sent. If this isn't important to the readers, then you don't need to close it. When a channel is no longer used, Go's garbage collector will free its resources, whether it's closed or not. Channel iteration range automatically reads the next value from the channel and checks if it's closed. If the channel is closed, it exits the loop: func main() { nums := generate(5, 10) for n := range nums { fmt.Print(n, " ") } } 5 6 7 8 9 Range over a channel returns a single value, not a pair, unlike range over a slice. Directional channels You can protect yourself from accidental write/close errors by setting the channel direction. Channels can be: chan (bidirectional): for reading and writing (default); chan<- (send-only): for writing only; <-chan (receive-only): for reading only. You can't read from a send-only channel or write to a receive-only channel (nor can you close it). Channels are usually initialized for both reading and writing, and specified as directional in function parameters. Go automatically converts a regular channel to a directional one: stream := make(chan int) go func(in chan<- int) { in <- 42 }(stream) func(out <-chan int) { fmt.Println(<-out) }(stream) 42 Buffered channels Buffered channels work like a FIFO queue with a fixed-size buffer for storing values. As long as the buffer has free space, writing to the channel doesn't block the goroutine. Similarly, as long as the buffer contains values, reading from the channel doesn't block the goroutine: stream := make(chan int, 3) stream <- 11 stream <- 12 stream <- 13 fmt.Println(<-stream) fmt.Println(<-stream) 11 13 By default, if you don't specify a buffer size, a channel is unbuffered (buffer size equals zero). Buffered channels work with the built-in len() and cap() functions: stream := make(chan int, 3) stream <- 11 fmt.Println(cap(stream), len(stream)) 3 1 Reading from a closed buffered channel returns values from the buffer and a true status. Once all values are taken, it returns a zero value and a false status, like a regular channel: stream := make(chan int, 1) stream <- 11 close(stream) val, ok := <-stream fmt.Println(val, ok) // 11 true val, ok = <-stream fmt.Println(val, ok) // 0 false 11 true 0 false nil channel Like any type in Go, channels have a zero value, which is nil. Writing to or reading from a nil channel blocks the goroutine indefinitely: var stream chan int go func() { // blocks forever stream <- 1 }() // blocks forever <-stream Closing a nil channel causes a panic: var stream chan int close(stream) // panic: close of nil channel # Select The select statement is somewhat like switch, but specifically designed for channels. Here's what it does: Checks which cases are not blocked. If multiple cases are ready, randomly selects one to execute. If all cases are blocked and there is a default case, executes it. If all cases are blocked and there is no default case, waits until one is ready. Select is used to manage data flow in pipelines: // merge sends values from in1 and in2 to the output channel. func merge(in1, in2 <-chan int) <-chan int { out := make(chan int) go func() { defer close(out) for in1 != nil || in2 != nil { select { case val1, ok := <-in1: if ok { out <- val1 } else { in1 = nil } case val2, ok := <-in2: if ok { out <- val2 } else { in2 = nil } } } }() return out } // Suppose we send 10..12 to in1, 20..22 to in2, // and call merge(in1, in2) 10 11 20 12 21 22 To cancel goroutines: // process modifies values from in and send them to out // until in is exhausted or cancel is closed. func process(cancel chan struct{}, in <-chan int) <-chan int { out := make(chan int) go func() { for val := range in { select { case out <- val*10: case <-cancel: fmt.Println("canceled") return } } }() return out } // Suppose we send values 11 and 12 to in // and then call close(cancel) 110 120 canceled For non-blocking operations: // multiplier returns a function that multiplies // the input by 10 and sends it to the channel // or returns an error if the channel is busy. func multiplier(ch chan<- int) func(n int) error { return func(n int) error { select { case ch <- n*10: return nil default: return errors.New("busy") } } } func main() { nums := make(chan int, 1) multiply := multiplier(nums) err := multiply(11) fmt.Println(<-nums, err) // 110 <nil> err = multiply(12) fmt.Println(<-nums, err) // 120 <nil> err = multiply(13) err = multiply(14) fmt.Println(err) // busy } 110 <nil> 120 <nil> busy And for much more. # Pipelines A pipeline is a sequence of operations where each step takes input data, processes it in a specific way, and outputs it. The input and output of each operation is a channel. A typical pipeline looks like this: Reader : Reads input data from a file, database, or network. N processors : Transform, filter, aggregate, or enrich data using external sources. Writer : Writes the processed data to a file, database, or network. func readT any <-chan T { out := make(chan T) go func() { defer close(out) for { // read data from somewere data := // ... out <- data } }() return out } func processT any <-chan T { out := make(chan T) go func() { defer close(out) for inData := range in { // process the data outData = // ... out <- outData } }() return out } func writeT any <-chan struct{} { done := make(chan struct{}) go func() { defer close(done) for data := range in { // write the data } }() return done } Output channel A goroutine can signal other goroutines that it has finished its work using an output channel : func generate(start, stop int) <-chan int { out := make(chan int) go func() { defer close(out) for i := start; i < stop; i++ { out <- i } }() return out } func main() { nums := generate(5, 10) for n := range nums { fmt.Print(n, " ") } } 5 6 7 8 9 Done channel If a goroutine doesn't need to return results, it can signal completion using a done channel : func work() <-chan struct{} { done := make(chan struct{}) go func() { defer close(done) fmt.Println("work done") }() return done } func main() { done := work() <-done } work done Cancel channel To terminate a goroutine early, a calling goroutine can use a cancel channel : func generate(cancel chan struct{}, n int) <-chan int { out := make(chan int) go func() { defer close(out) for i := 1; i <= n; i++ { select { case out <- i: case <-cancel: return } } }() return out } func main() { cancel := make(chan struct{}) defer close(cancel) nums := generate(cancel, 10) fmt.Println(<-nums) fmt.Println(<-nums) fmt.Println(<-nums) } 1 2 3 Error handling There are three approaches to error handling in concurrent pipelines. â Return on the first error: // calculate produces answers for the given numbers. func process(in <-chan int) (<-chan int, <-chan error) { out := make(chan Answer) errc := make(chan error, 1) go func() { defer close(out) for n := range in { ans, err := fetchAnswer(n) if err != nil { errc <- err // return with error return } out <- ans } errc <- nil // return with nil }() return out, errc } â Use a result type: // Result contains an answer or an error. type Result struct { answer int err error } // calculate produces answers for the given numbers. func calculate(in <-chan int) <-chan Result { out := make(chan Result) go func() { defer close(out) for n := range in { ans, err := fetchAnswer(n) out <- Result{ans, err} // return answer + error } }() return out } â Collect errors separately: // calculate produces answers for the given numbers. func calculate(in <-chan int, errc chan<- error) <-chan int { out := make(chan Answer) go func() { defer close(out) for n := range in { ans, err := fetchAnswer(n) if err == nil { out <- ans // send answer } else { errc <- err // or error } } }() return out } # Time Besides handling date and time, the time package offers tools for managing time-sensitive operations in concurrent programs. After time.After() returns a channel that is initially empty, but receives a value after the timeout period. It's useful for timing out operations: // withTimeout executes a function with a given timeout. func withTimeout(timeout time.Duration, fn func()) error { done := make(chan struct{}) go func() { defer close(done) fn() }() // blocks until fn completes or the timer expires, // whichever happens first select { case <-done: return nil case <-time.After(timeout): return errors.New("timeout") } } withTimeout() waits for fn() to complete, but thanks to time.After(), it won't wait longer than the timeout duration: func main() { var err error // completes in time err = withTimeout( 50*time.Millisecond, func() { fmt.Println("work done") }, ) fmt.Println("err =", err) // gets canceled on timeout err = withTimeout( 50*time.Millisecond, func() { time.Sleep(100 * time.Millisecond) fmt.Println("work done") }, ) fmt.Println("err =", err) } work done err = <nil> err = timeout Timer A timer (time.Timer) is a structure with a C channel to which it sends the current time when it triggers (expires). Timers are useful for planning future executions: done := make(chan struct{}) timer := time.NewTimer(50 * time.Millisecond) go func() { eventTime := <-timer.C // blocks for 50ms fmt.Println("work done at", eventTime) close(done) }() <-done work done at 2009-11-10 23:00:00.05 Stop() stops the timer and returns true if it hasn't expired yet, and false otherwise: // timer expires after 50ms timer := time.NewTimer(50 * time.Millisecond) go func() { eventTime := <-timer.C fmt.Println("work done at", eventTime) }() // after 10ms, the timer hasn't expired yet time.Sleep(10 * time.Millisecond) if timer.Stop() { fmt.Println("execution canceled") } else { fmt.Println("too late to cancel") } execution canceled It's often more convenient to use the time.AfterFunc() wrapper function. It waits for duration d and then executes function f: done := make(chan struct{}) work := func() { fmt.Println("work done") close(done) } // executes work after 50ms time.AfterFunc(50*time.Millisecond, work) <-done work done time.AfterFunc() returns a timer that you can cancel before execution starts: // executes the function after 50ms timer := time.AfterFunc(50*time.Millisecond, func() {}) // after 10ms, the timer hasn't expired yet time.Sleep(10 * time.Millisecond) if timer.Stop() { fmt.Println("execution canceled") } execution canceled If a timer is used in a loop, it's better to create a single timer and reset it instead of creating a new instance on each iteration: // consumer reads tokens from the input channel and alerts // if a value does not appear in a channel after an hour. func consumer(in <-chan token) { const timeout = time.Hour timer := time.NewTimer(timeout) for { timer.Reset(timeout) select { case <-in: // do stuff case <-timer.C: // log warning } } } // Suppose we send 10,000 values to the in channel // and measure memory usage. Memory used: 4 KB, # allocations: 6 Ticker A ticker is like a timer, but it keeps firing until you stop it. Tickers are useful for executing periodic tasks: // fires every 50ms ticker := time.NewTicker(50 * time.Millisecond) defer ticker.Stop() go func() { for { // waits for ticker to fire on each iteration at := <-ticker.C fmt.Println("work done at", at) } }() // enough time for the ticker to fire 3 times time.Sleep(160*time.Millisecond) ticker.Stop() work done at 2009-11-10 23:00:00.05 work done at 2009-11-10 23:00:00.10 work done at 2009-11-10 23:00:00.15 NewTicker(d) creates a ticker that sends the current time to the channel C at interval d. You must stop the ticker eventually with Stop() to free up resources. If the channel reader can't keep up with the ticker, the ticker will skip ticks. # Context The main purpose of context is to cancel operations, either manually or by timeout/deadline. The function accepts a context and uses its Done() channel to listen for cancellation: // work performs a task for 50 ms unless canceled. // Returns an error when canceled. func work(ctx context.Context) error { done := make(chan struct{}) go func() { time.Sleep(50 * time.Millisecond) fmt.Println("work done") close(done) }() select { case <-done: return nil case <-ctx.Done(): return ctx.Err() } } Cancel manually (context.Canceled error): func main() { // empty context ctx := context.Background() // manual canellation context ctx, cancel := context.WithCancel(ctx) defer cancel() done := make(chan struct{}) go func() { // takes 50 ms unless canceled err := work(ctx) fmt.Println("err =", err) close(done) }() // cancels after 10 ms time.Sleep(10 * time.Millisecond) cancel() <-done } err = context canceled Cancel by timeout (context.DeadlineExceeded error): func main() { ctx := context.Background() // cancels after 10 ms ctx, cancel := context.WithTimeout(ctx, 10*time.Millisecond) defer cancel() done := make(chan struct{}) go func() { // takes 50 ms unless canceled err := work(ctx) fmt.Println("err =", err) close(done) }() <-done } err = context deadline exceeded Cancel by deadline (context.DeadlineExceeded error): func main() { ctx := context.Background() // cancels at now + 10 ms deadline := time.Now().Add(10 * time.Millisecond) ctx, cancel := context.WithDeadline(ctx, deadline) defer cancel() done := make(chan struct{}) go func() { // takes 50 ms unless canceled err := work(ctx) fmt.Println("err =", err) close(done) }() <-done } err = context deadline exceeded Context is layered. A context object is immutable. To add new properties to a context, a new (child) context is created based on the old (parent) context. The shorter timeout between the parent and child contexts always wins. The child context can only shorten the parent's timeout, not extend it: func main() { // parent context with a 100 ms timeout const dur100ms = 100 * time.Millisecond parentCtx, cancel := context.WithTimeout(context.Background(), dur100ms) defer cancel() // child context with a 10 ms timeout const dur10ms = 10 * time.Millisecond childCtx, cancel := context.WithTimeout(parentCtx, dur10ms) defer cancel() // now the work gets canceled err := work(childCtx) fmt.Println("err =", err) } err = context deadline exceeded Multiple cancels are safe. You can call cancel() on the context as many times as you want. The first cancel will work, and the rest will be ignored. You can specify a custom cancellation cause using context.WithCancelCause(), context.WithTimeoutCause() and context.WithDeadlineCause(). This cause is accessible through context.Cause(): ctx, cancel := context.WithCancelCause(context.Background()) cancel(errors.New("the night is dark")) fmt.Println(context.Cause(ctx)) the night is dark You can register a function to execute when the context is canceled with context.AfterFunc(): ctx, cancel := context.WithCancel(context.Background()) cleanup := func() { fmt.Println("cleanup") } context.AfterFunc(ctx, cleanup) cancel() time.Sleep(10 * time.Millisecond) cleanup Context can pass additional information about a call using context.WithValue(), which creates a context with a value for a specific key. But it's generally better to avoid passing values in context. It's better to use explicit parameters or custom structs instead. # Wait groups The sync.WaitGroup type lets you wait for one or more goroutines to finish: const n = 10 var wg sync.WaitGroup wg.Add(n) for range n { go func() { defer wg.Done() fmt.Print(".") }() } wg.Wait() .......... A WaitGroup doesn't know anything about the goroutines it manages. It works with an internal counter. Calling wg.Add(1) increments the counter by one, while wg.Done() decrements it. wg.Wait() blocks the calling goroutine until the counter reaches zero. The Go method combines Add, starting a goroutine, and Done: var wg sync.WaitGroup for range 10 { wg.Go(func() { fmt.Print(".") }) } wg.Wait() .......... All methods are safe to use from multiple goroutines. Normally, all Add calls happen before Wait. But technically, there's nothing stopping you from doing some of the Add calls before Wait and some after (from another goroutine). You can call Wait from multiple goroutines. They will all block until the group's counter reaches zero. # Data races A data race happens when multiple goroutines access shared data, and at least one of them modifies it. We need to protect the data from this kind of concurrent access. A data race doesn't always cause a runtime panic. That's why Go provides a special tool called the race detector. You can turn it on with the race flag, which works with the test, run, build, and install commands. var total int // There's a data race on total. var wg sync.WaitGroup wg.Go(func() { total++ }) wg.Go(func() { total++ }) wg.Wait() fmt.Println("total:", total) total: 2 go run -race main.go ================== WARNING: DATA RACE ... 2 Found 1 data race(s) Channels are safe for concurrent reading and writing, and they don't cause data races. Ways to prevent data races: Avoid concurrent data modification (typically by using channels). Synchronize access with mutexes. Use only atomic operations. Race conditions A race condition happens when an unpredictable order of operations from multiple goroutines leads to an incorrect system state: // There's a race condition when working with balance. withdraw := func(amount int) { if getBalance() < amount { return } time.Sleep(time.Millisecond) setBalance(getBalance() - amount) } setBalance(50) var wg sync.WaitGroup wg.Go(func() { withdraw(40) }) wg.Go(func() { withdraw(40) }) wg.Wait() fmt.Println("balance:", getBalance()) balance: -30 If individual operations are concurrent-safe, Go's race detector won't find any issues. Because of this, it doesn't catch race conditions: go run -race main.go balance: -30 You can't fully eliminate uncertainty in a concurrent environment. Events will happen in an unpredictable order â that's just how concurrency works. However, you can prevent a race condition â often by protecting a composite operation with a mutex: var mu sync.Mutex withdraw := func(amount int) { mu.Lock() defer mu.Unlock() if getBalance() < amount { return } time.Sleep(time.Millisecond) setBalance(getBalance() - amount) } setBalance(50) var wg sync.WaitGroup wg.Go(func() { withdraw(40) }) wg.Go(func() { withdraw(40) }) wg.Wait() fmt.Println("balance:", getBalance()) balance: 10 Compare-and-set Sometimes you can prevent a race condition without using mutexes by applying an atomic compare-and-set operation or one of its flavors: // CompareAndSet changes the value to new if the current value equals old. // Returns true if the value was changed. CompareAndSet(old, new any) bool // CompareAndSwap changes the value to new if the current value equals old. // Returns the old value. CompareAndSwap(old, new any) any // CompareAndDelete deletes the value if the current value equals old. // Returns true if the value was deleted. CompareAndDelete(old any) bool // etc The idea is always the same: Check if the assumed (old) state matches reality. If it does, change the state to new. If not, do nothing. # Mutexes The sync.Mutex type protects shared data and parts of your code from being accessed concurrently: var total int var mu sync.Mutex var wg sync.WaitGroup for range 100 { wg.Go(func() { mu.Lock() time.Sleep(time.Millisecond) total++ mu.Unlock() }) } wg.Wait() total: 100 The mutex guarantees that only one goroutine can run the code between Lock() and Unlock() at a time. A mutex is used in these situations: When multiple goroutines are modifying the same data. When one goroutine is modifying the data and others are reading it. If all goroutines are only reading the data, you don't need a mutex. TryLock The TryLock method tries to lock the mutex, just like a regular Lock. But if it can't, it returns false right away instead of blocking the goroutine: var total int var mu sync.Mutex var wg sync.WaitGroup for range 100 { wg.Go(func() { if !mu.TryLock() { return } defer mu.Unlock() time.Sleep(time.Millisecond) total++ }) } wg.Wait() total: 1 RWMutex The sync.RWMutex type distinguishes between readers and writers. It provides two sets of methods: Lock / Unlock lock and unlock the mutex for both reading and writing. RLock / RUnlock lock and unlock the mutex for reading only. var total int var mu sync.RWMutex var wg sync.WaitGroup // 10 writers. for range 10 { wg.Go(func() { mu.Lock() defer mu.Unlock() time.Sleep(time.Millisecond) total++ }) } // 10 readers. for range 10 { wg.Go(func() { // Try switching from RLock/RUnlock to Lock/Unlock //and see how it affects the elapsed time. mu.RLock() defer mu.RUnlock() time.Sleep(time.Millisecond) _ = total }) } wg.Wait() elapsed: 10ms Here's how it works: If a goroutine locks the mutex with Lock(), other goroutines will be blocked if they try to use Lock() or RLock(). If a goroutine locks the mutex with RLock(), other goroutines can also lock it with RLock() without being blocked. If at least one goroutine has locked the mutex with RLock(), other goroutines will be blocked if they try to use Lock(). This creates a "single writer, multiple readers" setup. Locker Both sync.Mutex and sync.RWMutex implement the same sync.Locker interface: type Locker interface { Lock() Unlock() } By using Locker instead of a specific mutex type, you can build components that don't depend on a specific lock implementation. This lets the client decide which lock to use. Channel as mutex You can use a channel instead of a mutex to protect shared data: var total int lock := make(chan struct{}, 1) var wg sync.WaitGroup wg.Go(func() { lock <- struct{}{} defer func() { <-lock }() total++ }) wg.Go(func() { lock <- struct{}{} defer func() { <-lock }() total++ }) wg.Wait() total: 2 # Semaphores A semaphore is like a container with N available slots and two operations: acquire to take a slot and release to free a slot. Here are the semaphore rules: Calling acquire takes a free slot. If there are no free slots, acquire blocks the goroutine that called it. Calling release frees up a previously taken slot. If there are any goroutines blocked on acquire when release is called, one of them will immediately take the freed slot and unblock. You can implement a simple semaphore with a buffered channel, where N is the channel's size. To acquire the semaphore, send a value into the channel. To release it, take a value from the channel: // Try changing nConc and see how the elapsed time changes. const nConc = 4 const nCalls = 100 sema := make(chan struct{}, nConc) var wg sync.WaitGroup for range nCalls { sema <- struct{}{} // acquire wg.Go(func() { defer func() { <-sema }() // release time.Sleep(time.Millisecond) // do some work }) } wg.Wait() elapsed: 25ms For more complex situations, use the golang.org/x/sync/semaphore package. Rendezvous A rendezvous lets two goroutines wait for each other: There are two goroutines â G1 and G2 â and each one can signal that it's ready. If G1 signals but G2 hasn't yet, G1 blocks and waits. If G2 signals but G1 hasn't yet, G2 blocks and waits. When both have signaled, they both unblock and continue running. You can implement a simple rendezvous with a wait group: var rend sync.WaitGroup rend.Add(2) var wg sync.WaitGroup wg.Go(func() { fmt.Println("before rendezvous") rend.Done() rend.Wait() fmt.Println("after rendezvous") }) wg.Go(func() { fmt.Println("before rendezvous") rend.Done() rend.Wait() fmt.Println("after rendezvous") }) wg.Wait() before rendezvous before rendezvous after rendezvous after rendezvous Barrier A barrier is a general case of a rendezvous. It lets N goroutines wait for each other: The barrier has a counter (starting at 0) and a threshold N. Each goroutine that reaches the barrier increases the counter by 1. The barrier blocks any goroutine that reaches it. Once the counter reaches N, the barrier unblocks all waiting goroutines. You can implement a simple barrier with a wait group: const n = 4 var bar sync.WaitGroup bar.Add(n) var wg sync.WaitGroup for range n { wg.Go(func() { fmt.Println("before the barrier") bar.Done() bar.Wait() fmt.Println("after the barrier") }) } wg.Wait() before the barrier before the barrier before the barrier before the barrier after the barrier after the barrier after the barrier after the barrier # Signaling The sync.Cond (conditional variable) type lets one goroutine signal to another that it's ready, and lets the other goroutine wait for that signal. A Cond includes a mutex and has two methods â Wait and Signal. Wait unlocks the mutex and suspends the goroutine until it receives a signal. Signal wakes the goroutine that is waiting on Wait. When Wait wakes up, it locks the mutex again. cond := sync.NewCond(&sync.Mutex{}) done := false var wg sync.WaitGroup wg.Go(func() { cond.L.Lock() fmt.Println("G1 is ready to signal") done = true cond.Signal() cond.L.Unlock() }) wg.Go(func() { cond.L.Lock() for !done { cond.Wait() } fmt.Println("G2 received the signal") cond.L.Unlock() }) wg.Wait() G1 is ready to signal G2 received the signal If there are multiple waiting goroutines when Signal is called, only one of them will be resumed. If there are no waiting goroutines, Signal does nothing. You can also use the Broadcast method. While Signal wakes up only one goroutine waiting on Cond.Wait, the Broadcast method wakes up all such goroutines. You can signal with a channel: signal := make(chan struct{}, 1) go func() { // do something signal <- struct{}{} }() go func() { <-signal // do something }() And broadcast too: broadcast := make(chan struct{}) go func() { // do something close(broadcast) }() go func() { <-broadcast // do something }() go func() { <-broadcast // do something }() Broadcasting with a condition variable is limited: it only sends a signal, not the actual data, and it only works once. With channels, you can build a publish/subscribe system that doesn't have these limitations: type Publisher struct { sbox []chan int // subscription channels mu sync.Mutex // protects the state } func (p *Publisher) Subscribe() <-chan int { p.mu.Lock() defer p.mu.Unlock() sub := make(chan int, 1) p.sbox = append(p.sbox, sub) return sub } func (p *Publisher) Broadcast(v int) { p.mu.Lock() defer p.mu.Unlock() for _, sub := range p.sbox { select { case sub <- v: default: } } } # Run once The sync.Once type makes sure that the given function runs only once. If multiple goroutines call Once.Do at the same time, only one will run the function, while the others will wait until it returns: total := 0 initState := func() { total += 1 } var once sync.Once var wg sync.WaitGroup wg.Go(func() { once.Do(initState) // do something }) wg.Go(func() { once.Do(initState) // do something }) wg.Wait() total: 1 Once is perfect for one-time initialization or cleanup in a concurrent environment. Besides the Once type, the sync package also includes three convenience once-functions: // Calls f only once. func (o *Once) Do(f func()) // Returns a function that calls f only once. func OnceFunc(f func()) func() // Returns a function that calls f only once // and returns the value from that first call. func OnceValue T) func() T // Returns a function that calls f only once // and returns the pair of values from that first call. func OnceValues[T1, T2 any](f func() (T1, T2)) func() (T1, T2) # Object pool
The
sync.Pooltype helps reuse memory instead of allocating it every time, which reduces the load on the garbage collector:pool := sync.Pool{ New: func() any { buf := make([]byte, 1024) return &buf }, } // Only allocates 4*1024 B, despite 4000 loop iterations. var wg sync.WaitGroup for range 4 { wg.Go(func() { for range 1000 { buf := pool.Get().(*[]byte) sink = buf pool.Put(buf) } }) } wg.Wait() Memory allocated: 4 KBGettakes an item from the pool. If there are no available items, it creates a new one usingNew(which we have to define ourselves, since the pool doesn't know anything about the items it creates).Putreturns an item back to the pool.Things to keep in mind:
Newshould return a pointer, not a value, to reduce memory copying and avoid extra allocations.- The pool has no size limit. If you start 1000 more goroutines that all call
Getat the same time, 1000 more buffers will be allocated. - After an item is returned to the pool with
Put, you shouldn't use it anymore (since another goroutine might already have taken and started using it).
# Atomics
An operation without synchronization can only be truly atomic if it translates to a single processor instruction. Such operations don't need locks and won't cause issues when called concurrently (even the write operations).
There are only a few atomics, and they're all found in the
sync/atomicpackage:Int32 Bool Int64 Value Uint32 Pointer Uint64Each atomic type provides the following methods:
Loadreads the value of a variable.Storesets a new value.Swapsets a new value (likeStore) and returns the old one.-
CompareAndSwapsets a new value only if the current value is still what you expect it to be.var n atomic.Int32 n.Store(10) swapped := n.CompareAndSwap(10, 42) fmt.Println("CompareAndSwap 10 -> 42:", swapped) fmt.Println("n =", n.Load())
CompareAndSwap 10 -> 42: true n = 42
Numeric types also provide an
Addmethod that increments the value by the specified amount.All methods are either translated into a single CPU instruction or are otherwise guaranteed to be atomic, so they are safe to use from multiple goroutines.
The composition of atomics is always non-atomic:
var delta atomic.Int32 var counter atomic.Int32 func increment() { // Not atomic; causes a race condition. delta.Add(1) sleep(10) counter.Add(delta.Load()) } // After 100 concurrent increments, // the final value is NOT guaranteed. counter = 9386A bulletproof way to make a composite operation atomic and prevent race conditions is to use a mutex:
var delta int32 var counter int32 var mu sync.Mutex func increment() { // Atomic; doesn't cause a race condition. mu.Lock() delta += 1 sleep(10) counter += delta mu.Unlock() } // After 100 concurrent increments, the final value is guaranteed: // counter = 1+2+...+100 = 5050 counter = 5050Sometimes you can use an atomic type instead of a mutex to exit early:
type Gate struct { closed atomic.Bool } func (g *Gate) Close() { if !g.closed.CompareAndSwap(false, true) { return // ignore repeated calls } // The gate is closed. // We can free resources now. }# Testing
If your concurrent program uses channels or custom types with synchronization methods like
Wait, you can use those in your tests. This way, your tests won't be much more complicated than if the code were synchronous:// Calc calculates something asynchronously. func Calc() <-chan int { out := make(chan int, 1) go func() { out <- 42 }() return out } func Test(t *testing.T) { // Wait for the Calc goroutine to finish. got := <-Calc() if got != 42 { t.Errorf("got: %v; want: 42", got) } } PASSIf there aren't any suitable synchronization "handles" in the code you're testing, you can use the
synctestpackage. It exports two functions:func Test(t *testing.T, f func(*testing.T)) func Wait()synctest.Testruns an isolated bubble. The bubble uses a fake clock, and you can manually control goroutine synchronization withsynctest.Wait.synctest.Waitblocks until all goroutines in the bubble â except the one that calledWaitâ have either finished or are durably blocked. This lets you wait for a specific goroutine to finish or get blocked, so you can check the program's state:// NewProc starts the calculation. func NewProc() *Proc { p := &Proc{done: make(chan struct{})} go func() { p.res = 42 <-p.done // (X) p.res = 0 }() return p } func Test(t *testing.T) { synctest.Test(t, func(t *testing.T) { p := NewProc() defer p.Stop() // Wait for the goroutine to block at point X. synctest.Wait() if got := p.Res(); got != 42 { t.Fatalf("got %v, want 42", got) } }) } PASSThe fake clock in
synctest.Testmove forward only if: â all goroutines in the bubble are durably blocked; â there's a future moment when at least one goroutine will unblock; and âsynctest.Waitisn't running. Thanks to this, time-dependent tests run instantly:// Calc processes a value from the input channel. // Times out if no input is received after 3 seconds. func Calc(in chan int) (int, error) { select { case v := <-in: return v * 2, nil case <-time.After(3 * time.Second): return 0, ErrTimeout } } func Test(t *testing.T) { synctest.Test(t, func(t *testing.T) { ch := make(chan int) got, err := Calc(ch) // runs instantly if err != ErrTimeout { t.Errorf("got: %v; want: %v", err, ErrTimeout) } if got != 0 { t.Errorf("got: %v; want: 0", got) } }) } PASSThe following operations durably block a goroutine:
- A blocking send or receive on a channel created within the bubble.
- A blocking select statement where every case is a channel created within the bubble.
- Calling
Cond.Wait. - Calling
WaitGroup.Waitif allWaitGroup.Addcalls were made inside the bubble. - Calling
time.Sleep.
Blocking on mutexes, I/O, or system calls is not considered durable, and the
synctestbubble can't handle them.# Scheduling
At the hardware level, CPU cores are responsible for running parallel tasks.
At the operating system level, a thread is the basic unit of execution. There are usually many more threads than CPU cores, so the operating system's scheduler decides which threads to run and which ones to pause.
At the Go runtime level, a goroutine is the basic unit of execution. The runtime scheduler runs a fixed number of OS threads, often one per CPU core. There can be many more goroutines than threads, so the scheduler decides which goroutines to run on the available threads and which ones to pause. The scheduler keeps switching between goroutines to make sure each one gets a turn to run on a thread, instead of waiting in line forever.
CPU OS Go runtime ââââââââââââ run on ââââââââââââ run on ââââââââââââââ â Cores â <ââââââ â Threads â <ââââââ â Goroutines â ââââââââââââ ââââââââââââ ââââââââââââââThis is how Go handles concurrency.
Goroutine scheduler
The goroutine scheduler's job is to run M goroutines on N operating system threads, where M can be much larger than N. Here's a very simplified version of it's algorithm:
- If there's a free thread, assign it a goroutine from the queue.
- If a running goroutine gets blocked (for example, while reading from a channel), put it back in the queue and assign a different goroutine to the thread.
- If a running goroutine gets stuck in a syscall, start a new thread to run other goroutines until the blocked goroutine finishes the syscall.
-
Check the running goroutines every 10 ms. Preempt long-running goroutines and return them to the queue to prevent starvation.
ââââââââââââââââââââââââââââ â G17 ââ G18 ââ G19 ââ G20 â queue ââââââââââââââââââââââââââââ
âââââââ âââââââ âââââââ âââââââ â G15 â â G16 â â G13 â â G14 â running âââââââ âââââââ âââââââ âââââââ â â â â ââââââââââââ ââââââââââââ ââââââââââââ ââââââââââââ â Thread E â â Thread F â â Thread C â â Thread D â ââââââââââââ ââââââââââââ ââââââââââââ ââââââââââââ
âââââââ âââââââ â G11 â â G12 â syscalls âââââââ âââââââ â â ââââââââââââ ââââââââââââ â Thread A â â Thread B â ââââââââââââ ââââââââââââ
The number of threads running Go code is controlled by the
GOMAXPROCSenvironment variable or theruntime.GOMAXPROCSfunction.A goroutine is a structure that starts out using about 2 KB of memory, mostly for its stack. The stack can grow if needed. Since goroutines are so lightweight, you can run tens of thousands or even hundreds of thousands of them on a small machine.
# Diagnostics
To troubleshoot concurrent programs in production, we use metrics, profiling, and tracing.
Metrics show how the Go runtime is performing, like how much heap memory it uses or how long garbage collection pauses take. Each metric has a unique name and a value, which can be a number or a histogram.
You can use the
runtime/metricspackage to get a complete list of metrics or check the values of specific ones:samples := []metrics.Sample{ {Name: "/sched/gomaxprocs:threads"}, {Name: "/sched/goroutines:goroutines"}, } metrics.Read(samples) for _, s := range samples { fmt.Printf("%s: %v\n", s.Name, s.Value.Uint64()) } /sched/gomaxprocs:threads: 8 /sched/goroutines:goroutines: 1In practice, people rarely do this manually. Instead, all metrics are automatically exported using Prometheus or OpenTelemetry libraries.
Profiling helps you understand exactly what the program is doing, what resources it uses, and where in the code this happens. Go uses a sampling profiler that's suitable for production.
The most commonly used profiles are CPU, which shows how much processor time each function uses, and heap, which shows how much heap memory each function uses. Goroutine, block, and mutex profiles help identify problems related to concurrency.
The easiest way to add a profiler to your app is by using the
net/http/pprofpackage. To collect a profile with the given name, call the/debug/pprof/{name}endpoint. To view the collected profile, use thego tool pprofutility:go tool pprof -proto \ "http://localhost:6060/debug/pprof/profile?seconds=N" > cpu.pprof go tool pprof -http=localhost:8080 cpu.pprofYou can also profile manually:
// CPU profile. file, _ := os.Create("cpu.prof") defer file.Close() pprof.StartCPUProfile(file) defer pprof.StopCPUProfile() // ... // Any other profile. file, _ := os.Create(name + ".prof") defer file.Close() pprof.Lookup(name).WriteTo(file, 0)Tracing records certain types of events while the program is running, mainly those related to concurrency and memory. When the profiling server from the
net/http/pprofpackage is running, call the/debug/pprof/traceendpoint to collect a trace. To view the results, use thego tool traceutility.You can also collect a trace manually:
file, _ := os.Create("trace.out") defer file.Close() trace.Start(file) defer trace.Stop() // ...You can set up automatic tracing with a sliding window that's limited by size or duration. This is called "flight recording". It lets you always keep a recent trace available in case something goes wrong:
cfg := trace.FlightRecorderConfig{ MinAge: 5 * time.Second, MaxBytes: 3 << 20, // 3MB } rec := trace.NewFlightRecorder(cfg) rec.Start() defer rec.Stop()# Final thoughts
We've covered a number of Go tools for writing concurrent programs:
- Goroutines for running concurrent tasks.
- Channels and select as flexible communication tools.
- Timers and tickers for working with time.
- Context for canceling operations.
- Wait groups for synchronizing goroutines.
- Mutexes to prevent race conditions.
- Condition variables for signaling events.
- Once for safe one-time initialization.
- Pools to reduce garbage collector load.
- Atomic operations.
If you like the book, please recommend it to your friends or colleagues. If you're interested, check out my other books and projects.
I'm glad you finished the book. Thank you, and I'll see you next time!
-
đ smol-machines/smolvm smolvm v1.19.1 release
What's Changed
- Let a cascade delete finish when a clone vanishes mid-cascade by @LoganGrasby in #1419
- fix: propagate machine exec database lookup errors by @sgrove in #1331
- Reconcile bounded late VM exit after shutdown acknowledgment failure by @sgrove in #1329
- Size the pack VM's storage disk from the image manifest by @Bnjoroge1 in #1322
- Resolve image manifests from the reference's own registry only by @BinSquare in #1420
- Link the Node, Python and Rust SDKs from the README by @BinSquare in #1421
- Restructure the README around what smolvm is for by @BinSquare in #1423
- Honor a local image's WORKDIR, ENV and USER in machine run by @BinSquare in #1424
- Specify the checkpoint format and name checkpoint files .checkpoint by @BinSquare in #1422
- Bump the workspace to 1.19.1 by @BinSquare in #1425
Full Changelog :
v1.19.0...v1.19.1 -
đ hacker news ida pro references New comment by zellkernel in "Reverse Engineering Fortinet with Ablation" rss
Ablation is a reverse engineering framework that provides the exact same core disassembly, decompilation, and binary analysis capabilities as industry-standard tools like Ghidra, IDA Pro, and Binary Ninja. Combined with an LLM, it transforms into a fully autonomous reverse engineering tool.
Ablation is built for the modern landscape, and more importantly, the human.
Now reverse engineering is accessible to anyone. No matter your wallet or your barrier of entry into education, you can learn about reverse engineering as you reverse engineer.
-
đ Register Spill Joy & Curiosity #101 rss
Last week I was on Matt Swanson's podcast and we ended up sharing thoughts and vague predictions about programming languages and frameworks. Matt said that he'd be going to Rails World the week after and that he wouldn't be surprised if DHH said "Rails is over." Prescient. DHH didn't exactly say that Rails is over, but, well, some people did call the keynote a "funeral."
But that shouldn't be surprising, right? The way we've treated languages and frameworks for the past, say, twenty years is at odds with the fact that writing code by hand is on its way out.
I am not sure how exactly this will play out, but here are some loose thoughts:
-
Frameworks are no longer the biggest developer productivity lever. Agents are a hundred times bigger.
-
Syntax doesn't really matter anymore, as long as agents can write it well.
-
Tool ergonomics don't matter that much either, do they? Previously, I loved that
gocomes withgo buildandgo testandgo run, but now I wouldn't care if those commands were seventeen times longer. -
But I think shared abstractions are still worth it. A framework gives you a pre-defined way to access a database, to do auth, to divide things into production and development⌠That's still handy. Not because it would cost tokens to build it myself (won't matter in the future, see below), but because I just don't want to think about it.
-
What will matter a lot in the future: performance characteristics, resource usage, failure modes, observability, debuggability, deployments, rollbacks. And all of that needs to be legible to the agent. I've tried developing something with the often praised Cloudflare Durable Objects, on which you can run JavaScript, and it was a disaster: the agent constantly thought it was writing normal Node.js JavaScript; it didn't know the runtime characteristics of the Durable Objects; it couldn't easily access the logs. In fact, there are no logs that tell you when your code gets evicted and when it gets resumed⌠You want the opposite to be the case: the agent should know from looking at the codebase how it will be executed and how it can see that.
-
The big force is that we're switching from "I prefer this language because I enjoy working with it" to "I prefer this language because my agent can get great results with it." Now, how would your preferences change if you switched from driving a car to controlling it remotely? You wouldn't care about heated seats and AC, would you? But you'd care about how fast it can brake, I assume.
-
Ecosystems will change. I can't remember the last time I browsed through GitHub to find a library to do a thing. The old NPM credo of "many, many tiny modules" seems even sillier now than it did ten years ago.
-
Sometimes I wonder whether the thing that makes some developers say that agents will change everything and others that they can't write good code is the language they used with the agent. 99% of the code I've had agents write was in TypeScript. Not a language I love, but, hey, who cares? And agents seem great at it. I wonder what my thoughts would be if I still were writing Rust.
-
Then again: I no longer think that "is the language well-represented in the training data?" matters as much as I thought it would. Intelligence generalizes and I've seen agents just crush custom DSLs that have not shown up in any data, ever. We previously said about humans that "if you learned 5 different languages, you kinda know them all" -- maybe that's what's going to happen with agents too? And it kinda makes sense, right? Why wouldn't a frontier model like Astra or Fable be able to use a new language as long as it can run it and have a feedback loop?
-
Very pessimistic on the future of "paper-over" languages and frameworks. You know: this language but with nicer syntax. CoffeeScript, if you're old enough to remember. Haml, Sass, Less -- not sure. What about Elm or ClojureScript? Hmm.
-
A lot of testing frameworks started with TDD in mind: you write a test in which you describe the behavior you want, you run the test to see it fail, you make it pass. Then, with growing adoption of tests, people started using those very same frameworks to add tests after everything already worked. Regression tests. Now we have the very same frameworks being used by agents to write tests god knows how and essentially no one looks at these billions of lines of test code that are generated every day now. Will we still have
describeanditblocks in ten years? Just like some terminal emulators still mention baud rates? I do think that the models will get so good that they don't "need" unit tests in the same way humans needed them: to make sure something works. But maybe they won't stop writing them and we'll end up with effectively useless tests piling up? -
I'd be incredibly surprised if formal methods and strong static typing had the boom that their fans say they will have. Vitamins, not painkillers; worse is better, etc.
-
I have three little apps that I use every day and that I had the agent write, and I have zero clue what language they're written in. I think it's JavaScript?
-
Porting from one language to another seems to be a completely different thing now compared to three years ago.
Again: I don't know where we'll end up, but I do think being aware of the forces at play is important. It's also fun stuff to think about.
Before we get to the J &C juice: I'll be in NYC with the Amp team Oct 5-11 and in SF the week after, Oct 12-17. My schedule will be busy and chaotic, but if you're around and want to grab a coffee, let me know!
-
We recorded a new Raising An Agent episode after I said to Quinn: "Man, I'm so full of hot takes today. I need to record a video." So, there it is: an episode full of hot takes. From a whole engineering org making everyone use Qwen, to why GDP isn't increasing if you aren't letting your agents go vertical , to why you're wasting time if you're waiting on your agent instead of the other way around, to why I've been very disappointed by a lot of "software engineers" in the last two years.
-
Expect this to continue: "If we combine all this, we see about 2.5 orders of magnitude decrease in token cost in the last year. Models are about 100x as cost-efficient per-task. Hardware is about 1.3x as energy-efficient per-token. Engines are about 1.4x as energy-efficient per-token." The strongest force in technology today. I stand by what I wrote in last week's predictions about the future of software development: "Tokens are the new computing paradigm. Everything will be re-made on top of it."
-
So, DHH lit the Ruby & Rails world on fire by giving a keynote in which he doesn't talk about Rails all that much. Instead, he said what others (hey , what's up) have said for at least the last six months: writing code by hand is over; these agents are really good; choosing Ruby over other languages due to its developer friendliness doesn't make a lot of sense anymore; old engineering tradeoffs should be revisited. But he said it in a way that only DHH can. He's very, very good at boiling things down to their essence and turning them into statements that make you choose whether you're for or against it. It's impressive, honestly. Read the Hacker News comments to get a taste. Now, predicting the next six months, I wonder when the Omarchy community will run into the question of: wait, so we now have this malleable operating system, but⌠if agents write and debug all the code, why do we even need it?
-
Thomas Dullien, or: Halvar Flake, gave a presentation: An age of experimentation. It's really good and I highly recommend you click through it. Here, to give you a taste: "Claim: Determinism is dying, and it's unclear how much will remain."
-
New Peter Thiel interview! Say about him what you want (and there's a lot to say), but his ability to read the vibes in the world seems to be rarely matched. What makes this interview also interesting is that the interviewer is Mathias Dopfner, quite the controversial billionaire himself. At some point in the interview, Thiel compares the twenty richest people under 30 in the US with those in Germany and says that the twenty in Germany all inherited their wealth. Guess how Dopfner got his shares of Axel Springer SE? Heavily discounted and some as gifts, from Springer's widow.
-
Attention is all you have: "If, like me and most people, you spend the major part of your day focused on your device, there's no doubt it's affecting you. And when you let someone else dictate what appears on your screen, it's the same as giving them the key to your brain."
-
"But there are pleasures to be had from books beyond being lightly entertained. There is the pleasure of being challenged; the pleasure of feeling one's range and capacities expanding; the pleasure of entering into an unfamiliar world, and being led into empathy with a consciousness very different from one's own; the pleasure of knowing what others have already thought it worth knowing, and entering a larger conversation."
-
Martin Fowler: I don't like LLMs. "I don't like them. They talk to me in this grating LLM-voice, an uncanny valley of talking to a real human. They confidently bullshit me - often giving me useful, helpful answers. But also just making stuff up with the same assurance - and with only a veneer of fake remorse when I call them out on it." He's got a point. Many times a day I think to myself "god, shut UP " when the model comes back with whatever the latest equivalent to "you're absolutely right!" is: spines, seams, belts and braces (or suspenders). But⌠less and less so? I parse their output more like a receipt I get handed in a shop or in a restaurant: skip the stuff at the top, ignore the gibberish at the bottom, zoom in on the stuff there in the middle.
-
I didn't know about the Moving Image Archive but was delighted when I came across its collection of animated maps.
-
This seems very neat and makes me want to build something with Go: Platform-independent SIMD.
-
"I text him that night and I said, 'Hey, I want to ask you a question. Can I coach you hard?' [âŚ] Then the next day he sees me in pre-practice, and I said, 'Just let me explain this. Do you see yourself being in the Hall of Fame someday?' And he said, 'Yeah.' And I said, 'All right. Well, I don't think your trajectory right now is steep enough to make that goal happen. I think your trajectory is five Pro Bowls, a couple All-Pro teams, phenomenal career, all-time leading rusher, but I think your trajectory, and I think your practices have to beâŚ' And then he starts complaining to me, and I said, 'I thought you told me I could coach you hard.' And then, you know, it hit him."
-
How to Unclench. I finally read this after it made a big splash last week. It's a much faster read than I thought it would be. It's like a neat little mini-book.
-
Robert O'Callahan: "I'm resigning from Google today. This has not been an easy decision. I love my colleagues and my work environment, and being paid handsomely to solve fun puzzles has been amazing. But my team's goal is ultimately to make AI much cheaper and lower-latency, and I don't think that's good for people right now: I firmly believe AI progress is currently far too rapid (and I have doubts about the destination too)."
-
"I started to explain how it happened when they cut me off with 'Michael, I don't want the details'." Good stuff.
-
Thomas H. Ptacek and Kurt Mackay are leaving fly.io to build a phone: "Today, almost all software comes from expert strangers. But soon, strangers will stop supplying our apps, and instead ship just their building blocks. Sure, there will still be megaproject browsers and word processors. But there'll be thousands of times more applications that pull in 1/7th of the guts of a word processor to solve some idiosyncratic work or home life problem for somebody who doesn't know what a for-loop is. [âŚ] That's what we're working on: a platform that is the device we would want to have in the world I just described. So: we're building a phone." I nod my head to the boldness of entering one of the most competitive markets of all time.
-
It's totally not the point of this Obie Fernandez post, but I can't stop thinking about this part here: "Trying to make a point, I hit enter to accept Fable's first suggestion. Minutes later I hit enter again, and then again, choosing to delete some dead code. We start making a PR. My friend asks me to make sure it's set to draft. Sure, whatever. Fable does its thing. My friend checks the diff. It's a simple deletion of dead code and associated unit tests. I want to push on, but my friend begs me to stop. 'You don't understand Obie, I can't just do what you're doing, man.' I challenge him to explain why not. He explains that he has a boss and teammates and that he can't just make changes like that, he has to present plans and execute on them." It made me remember what it feels like to work in such teams, where you can't make decisions alone, where you're not trusted to make a call like "I'm going to refactor this API" or "I'm going to delete this" or "I'm going to add a new feature that lets usâŚ" without having reached a consensus with the team. Remembering that made me feel sad, thinking: "wow, imagine what it's like to now have AI but no power, no trust, no freedom to use it?" There's no way around it: if you box AI into this little corner where all it can do is change code on your local machine and help you push it up as a PR, you're holding the leash at a fraction of its real size.
-
Intellectuals are F*cking Idiots: "Reality always wins. But Intellectuals are rewarded for their models, not reality. And the data and analysis that looks elegant on paper is often disastrous on the ground. Yet, when their models are contradicted by reality, most intellectuals don't have the courage to accept the reality, instead they double down on their models âŚand this is what turns them into idiots." If it's nothing else, this was entertaining!
-
I thought of this John Carmack post again, so here it is, again: "Make better decisions and fill your products with 'Give a Damn'!"
-
This is fantastic: Fixing the Portobello Police Station Clock. It's a hack, it's nerdy, it's real blogging. This is the type of stuff that made me fall in love with the Internet.
Thoughts on the future of programming languages? Subscribe here:
-
-
đ HexRaysSA/plugin-repository commits sync repo: +4 releases, -1 release rss
sync repo: +4 releases, -1 release ## New releases - [augur](https://github.com/0xdea/augur): 0.10.3 - [ida-rpc](https://github.com/bkerler/ida_rpc): 0.2.0 - [patching-ng](https://github.com/mahmoudimus/patching-ng): 0.5.0, 0.4.0 ## Changes - [patching-ng](https://github.com/mahmoudimus/patching-ng): - host changed: mahmoudimus/patching â mahmoudimus/patching-ng - removed version(s): 0.3.0
-
- September 25, 2026
-
đ smol-machines/smolvm smolvm v1.19.0 release
What's Changed
- Honor --credential when creating a machine from a pack by @BinSquare in #1400
- Pin the Nix flake to 1.18.2 with its real release hashes by @BinSquare in #1398
- Launch a credentialed machine with its credentials on every start and resume by @BinSquare in #1402
- Report a forked machine's own creation time by @LoganGrasby in #1407
- External egress interceptor by @ankrgyl in #1405
- Let a lease request wait for a clean pool worker by @LoganGrasby in #1408
- Keep the pool controller from retiring a worker a lease just claimed by @LoganGrasby in #1406
- Keep external interception fail closed across VM restarts by @BinSquare in #1409
- Let a virtio-net machine start after machine update --no-net by @LoganGrasby in #1410
- Upload captured checkpoints straight to object storage by @BinSquare in #1403
- Fail a Windows virtio-net launch when a published host port is in use by @nitzzzu in #1416
- Share one database handle per process by @LoganGrasby in #1417
- Retry a read-then-write DB transaction that loses the write race by @LoganGrasby in #1413
- Don't retire a paused leased fork pool worker by @LoganGrasby in #1414
- Refuse to implicitly start a paused machine by @LoganGrasby in #1415
- Retry an agent connect the guest hangs up during the handshake by @BinSquare in #1412
- Keep the guest agent answering pings while every work slot is busy by @BinSquare in #1411
- Take API credential values from the API, never from the host environment by @BinSquare in #1401
- Bump libkrun for the restored-inode, vsock restore, kqueue and fork generation fixes by @BinSquare in #1399
- Read the machine name from SMOLVM_MACHINE_NAME when --name is omitted by @BABTUNA in #1392
- Bump the workspace to 1.19.0 by @BinSquare in #1418
New Contributors
Full Changelog :
v1.18.2...v1.19.0 -
đ r/Harrogate Hedge & tree recommendations rss
Can anyone suggest reliable, responsive and available hedge and tree care people? I just moved to the area and need to line someone up for the annual hedge trim, but have reached out to three businesses so far and none have even replied
submitted by /u/LittleSadRufus
[link] [comments] -
đ Mr. Money Mustache Will the AI Bubble Destroy our Retirement? rss

Wow, how about that stock market?
Itâs a phrase we keep having to dust off and use again, as the years go by and the market keeps surprising us.
When it crashes, some of us worry because we see our retirement stash shrinking.
But even when it rises to record levels and then to super-duper-crazy record valuations, we find reason to worry. Because that just means an even bigger crash is coming, right? Especially when this boom-bubble is built on the back of something as frenzied as the present Artificial Intelligence boom⌠right?
For those who have been happily tuned out of this drama and just enjoying life: Congratulations! Keep up the great work. But just to give you a quick background for the purposes of this article, hereâs the AI story in three points:
- Over the past few years, AI has reached a level where it can do complex thinking and reasoning tasks that most of us thought might not happen in our lifetimes. Itâs shockingly useful.
- This has led to the fastest worldwide adoption of any technology in history: over 1.5 billion people are already using it, as are most large companies
-
And this has caused a crazy cycle of growing sales, investor enthusiasm and data center buildout that is now the largest investment cycle humans have ever made in anything: 1.8 trillion dollars has been spent since just 2022, with another trillion going out the door next year alone. Which you can compare to:
-
The cost of the 2700-foot-tall Burj Khalifa, the highest skyscraper in the world (about $2 billion in todayâs dollars)
- Or the entire US interstate highway system, 48,000 miles of wide, flat, highly engineered roads (and 55,000 massive bridges) which cross countless mountain ranges, rivers and canyons: $660 billion in todayâs dollars.
So whereâs the problem?
Investors worry that while AI is definitely a major invention, the mania around it is still too much, too fast, and thus we are due for an even bigger version of the dot com crash we had in the early 2000s. Which happens to be an interesting subject for me, since that was the boom that boosted my own early career and got me on the path to early retirement ⌠before the ensuing crash almost cost me my job and my citizenship application.
As an aging Internet Financial Guru, I have the privilege of getting lots of questions about this investment cycle, just as I have about past booms and busts. And to address them, I decided to not only write this blog article but also create a little talk about it - which I already gave for the first time at an event called Camp FI Midwest earlier this month. I also hope to polish it up and deliver an improved version at the Bogleheads conference in November.
So what that means is that today you not only get a blog post, but a few silly presentation slides to go with it for extra entertainment. All with no need for a plane ticket or all that hassle of leaving your house. So letâs get into it!
Will the AI Bubble Ruin our Retirement?
-So I recently hit 21 years of retirement. This means Iâve grown pretty comfortable with the idea. But it still threw me for a loop one time when a friend asked me this question:
âHow can you be retired, and comfortable in your retirement, and sleep at night, with everything thatâs going on in the world? Arenât you worried?â
And I was like, âNo, what should I be worried about?â
But if youâre a news watcher and a worrier yourself, you can probably think of a few things I should be worried about. Perhaps stuff like this:
2026 worriesYouâve got our corrupt and/or dangerous politicians like Trump and Putin menacing the world. Inflation eroding our purchasing power, the AI Bubble, the towering US National Debt, the Iran war and the Ukraine war and all these other wars that are just on the verge of tipping us all into destruction.
But it turns out these were not the things my friend was asking about.
Because she actually asked me this question over twenty years ago⌠In the year 2006. So back then we were worried about entirely different things, right?
2006 worriesBack then we had corrupt and/or dangerous leaders like Bush, Hussein and of course Bin Laden. There was still a war in the middle east but it was Iraq instead of Iran. We were still worried about the National Debt and Inflation. And we also worried about our overvalued stock market. But back then it was because of the housing bubble instead of the AI bubble. And instead of AI taking our jobs it was going to be outsourcing to India and China.
Oh and hereâs an interesting one: There was a big debate over Peak Oil, and whether the world was doomed because fossil fuel use was always increasing while supply was bound to decrease. And it turns out the opposite happened as you'll see below.
So then after all this worry in 2006, what ended up happening?
-Well we did get the Great Financial Crisis, which was caused by a combination of irrational exuberance over increasing house prices combined with a foolish degree of fancy leverage in the financial instruments used to issue mortgages. And it was the biggest crash since the Great Depression.
But even in that craziest of situations, look how minor it looks in the big picture. A $100,000 investment still ended up ballooning into $852,000 today. And if you look carefully in here you can just see the Covid Crash tucked in there in 2020. Remember that?
And not only that, the rest of the world got a lot better too:
The portion of people living in extreme poverty dropped from 20% in 2005 to only 8% over the next 20 years. Infant mortality - children who die in their first year of life - was cut in half. Clean energy became a thing as solar panels became about 40 times cheaper. So solar generation now makes up the vast majority of all the new generation we add today (the equivalent of 647 nuclear power plants of peak capacity added last year, but with much cheaper and easier solar panels). And sales of pure electric cars have gone from zero up to about a quarter of all the cars sold on Earth last year, on its way to 100%, which is where they already are in Norway.
So if we circle back to that question my friend asked me 20 years ago: do you think I should have focused my energy on worrying about an uncertain future, or optimism?
Which of course brings up the question: Is it different this time?
Maybe the US national debt is finally big enough to really start messing with us. Maybe the AI bubble is way bigger than the housing bubble and we wonât bounce back from this next stock market crash. Or maybe AI will spin out of control into a super intelligence that takes over the planet and realizes it does not need us any more.
And the financial media loves to spin a good scary story about all of this. But you know what the fear mongers all seem to be missing?
Itâs the fact that at the core, our prosperity does not come from the financial system or the stock market, which are just some made-up numbers on some computers.
Really, Prosperity comes from Productivity.
-We first covered this lesson right here on MMM, in the 2014 Classic entitled, âWhy we are not really all doomedâ. And sure enough, twelve years later the timeless lessons therein remain just as true now as they were back then.
And the lesson is really simple: the world of capitalism is always choppy and prone to wild swings. But if you peek beneath the waves, there are real people in there, doing real work and coming up with real inventions.
In fact, the very idea of an âeconomyâ or a financial system is just another human invention. If the whole system crashes because we donât like the numbers we see in there, we can literally make up some new numbers and then get back to work. Which is exactly what we did in 2008 and many times before that, and it seems to have worked just fine.
Meanwhile, we still have every tool we ever invented to boost our productivity. And our standard of living, and our economic growth, and our possibility of retiring early, all come from high productivity.
But even Productivity and Prosperity arenât all that important.
Because once we reach a certain standard of living, human happiness kind of tops out in that dimension. In first-world countries, we blew past those minimum requirements several decades ago. And then we start looking for other factors to maximize our overall happiness. All anybody really wants is to lead the happiest, most satisfying life they can manage
And when you think of it that way, your wealth is really just part of your life situation, which is part of this little green slice of the happiness pie.
Approximate non-scientific factors in happinessGenetics is unfortunately the biggest slice - some people are just plain happier than others. But there are also quite a few choices that are within your control.
Focusing on good close relationships and being kind to people. Practicing Gratitude and Optimism as your lifelong philosophy. And making sure you pack as many healthy (outdoor) activities into your days as you can.
So, I didnât invent anything new in this blog post. And most of it is just a repackaging of the same stuff Iâve been writing about since 2011. And yet some regular MMM readers who presumably know all this stuff are STILL afraid. Afraid to retire, afraid of things in their unknown future. Can we fix this?
I think the biggest problem is Fear Itself.
-The problem is that as a Human Being, you are basically a Danger Detection Machine. And this pervasive background fear is just a trait we evolved to protect ourselves from danger.
But thereâs a weird thing about our detection machinery. We can adapt and learn to function even in very dangerous environments, but when the danger goes away, we keep looking for stuff to worry about. In other words, there is a problem with fear: for many of us, it never really goes away. It just keeps moving the goalposts.
While Nature has wisely endowed us with a fear of predators and disease, if you solve those things youâll just start worrying about whether or not you can get enough food. And supplies, and protecting yourself against scarcity.
If you succeed, next youâll be worrying about if you can fit in with your tribe, because if you donât, you might be exiled.
And if you donât have to worry about that, youâll go back to worrying about money for different reasons. Thanks to our tendency to use comparisons rather than absolutes (sometimes called "anchoring bias" and the "contrast effect") when judging our lives, just getting out of poverty isnât enough. We just move on to wanting more money.
And then, when some people get enough money to have a comfortable upper- middle-class lifestyle, they take the logical next step of starting a Homeowner's Association, so they can worry about the unauthorized weeds on their neighborâs lawn. Or maybe a Political Action Committee in hopes of controlling the color of their future neighbors' skin.
And even if all the lawns and gardens are green and perfect and people get really rich, they just move on to worry about tax strategy, leaving a huge inheritance for their children, and avoiding RMDs.
Required minimum Distributions. This is when, if you get to 72 years old and you have too much money , the government will start making you withdraw some of that money so you can spend it and pay taxes on it. And people actually worry about this. I shit you not.
But wait! You can actually fight back against fear. Itâs just a bit tricky because it requires some self awareness. But you have the advantage, because you have the ability to think rationally. And your fear is quite clearly irrational.
-And the first step is to understand your own history. Are you afraid of certain things because of your childhood?
I might have been afraid of running out of money, because in my family there was always a perceived shortage of the stuff, possibly enforced by my Dadâs scarcity mentality. And he was aware that his own fears of scarcity came from childhood as well: he grew up in a truly low income family in the 1940s and 50s, under the care of parents who had lived through the Great Depression of the 1930s.
Even more significant is that survivors of trauma and abuse will very likely end up with fear issues in adulthood. Itâs natural, and unfortunately abuse is way more widespread even in this country than we would like to admit. This makes it harder to eliminate, but it still helps to understand that your past is what is causing these feelings, rather than an objective understanding of the present.
Identify the Fear: One you understand yourself, it helps to talk through the nature of your fear in more detail. This is also often taught in therapy. What would happen if it really came true? What would the worst case scenario be? Itâs often not all that bad.
In one post, why youâll probably never run out of money, I went to great lengths to imagine what it would mean for a wealthy person like yourself to actually let the well run dry, and letâs say youâll probably never come close.
Once you identify the fear, you can start taking action. And one of the best and most accurate slogans ever is that action cures fear. Itâs because it makes the unfamiliar, familiar. And you get confidence that you can really create change. And then that learning creates Resilience - the ability to adapt to ANY new situation, which is a trait also known as Badassity. And with sufficient Badassity, nothing is scary.
-Think about it: if you had the skills and abilities to handle ANY situation, skills in the mental and physical and even the realms of emotion and wisdom, would you really have to worry about anything?
The next thing you can do is tune out all the unnecessary crap from your daily mental diet. And for most Americans, this means the daily news.
The news is not helping you to keep informed, itâs just poisoning your mind with a hand-picked selection of the scariest stories of each day. If you want to learn about something, go find a book on the subject. And if you want to make a difference, the only thing that counts is not the news stories you watch - it's only the actions that you take - in other words, your Positive Behaviors.
As a human being, you are wired to solve problems with both your body and your mind, and you are meant to do it outdoors. So if you spend all your days in a house and a car and in an office using the computer, of course you are going to have some mental health problems. This should not be a surprising thing you need to ask your therapist about, it is an expected result of not living the life that you were literally created to live as a Human being!
So you need to focus on learning, moving, and solving problems. And you need your food and drink intake to be in alignment with all of this too. Life will get a lot less scary as soon as you are living life in the way you were built to live it.
Then finally, you can start swapping out the fear-mongers in your life, for other positive, productive, optimistic people. And watch how much the positive mentality rubs off on you. In the FI community, at least the subgroup of people that I like to spend time with, nobody focuses on fear. Itâs all just about living the best life we can, and helping other people do so as well.
So now that we know how to fight back against fear, we can start using a new approach when we go into anything new. And that approach is something I like to call Everything is an Experiment.
Everything is an ExperimentAnd this applies whether you are talking about something tiny like a new paint color. But it scales up to bigger things like switching to a new car, or a new house, meeting new friends, looking for new love by going through the sometimes hell of first dates, a new job, or even quitting your last job by taking an early retirement.
So let's bring this back around to AI, the thing that everybody seems to be afraid of right now.
I think of our new AI era as just being another giant experiment. And because of this, I donât find it even remotely scary. But I do find it endlessly fascinating.
First of all, itâs a change. A potentially unknown one, and for some people thatâs scary.
But instead of just leaving it there and then doom-scrolling ominous news articles about it, why not learn why people are so excited about AI? As someone with a lifelong background in technology and economics (and a general lack of political affiliation), I can see a lot more potential upside than downside. And that mainly comes from the fact that I see AI as just a very fancy form of Cognitive Nail Gun.
-It will boost our efficiency, which means our productivity, which means prosperity. Because itâs really just a super smart brain that we all get to tap into at a fairly negligible cost.
Unfortunately, it will also displace workers like every other new productivity technology. Itâs already doing so in things like entry level office jobs in software and finance. It will eventually replace car and truck drivers, and so on. But in exchange, these services will become cheaper, and new industries will be created because of the new technology. Much like the Internet itself, and then the enormous industries unleashed by smartphones and mobile apps.
And one unfortunate side effect of these new inventions is that they tend to concentrate their rewards on the people who own them. If you own a house building company and employ a bunch of carpenters and give nail guns to the best of those carpenters, you can now lay off the other half and still get more done. If you own a company and roll out AI to the workers, you can do the same thing.
And if youâre a big public company, your shareholders also benefit, which happens to include most of the people reading this. As youâve seen when looking at your portfolios over the past two years.
Artificial Intelligence is just like any other new thing that pops into your life: it's an opportunity to make some choices. And it is up to you to decide if this is something to be afraid of, or to use as a trigger for learning, action, and living a more interesting life.
So that's my little talk (and surprisingly long blog post) on Fear versus the AI bubble.
I hope that if you do feel fearful about this or any other issue in your future life, you'll take it as a cue to learn more about your own fear and emotions and manage them as the root cause they are, rather than just trying to avoid the symptoms. Because in the wise words of my friends The Donegans:
-Everything you want in life, that you don 't already have,
lies on the other side of Fear.
Because if you weren't afraid to go out there and get it, you would already have it. -
đ Hex-Rays Blog Hex-Rays IDA MCP Server rss
We are happy to introduce the official IDA MCP server! This free and open source software connects your IDA installation with AI agents, enabling them to disassemble, decompile, and support reverse engineering tasks. It works great with models like Gemini, Qwen, or Opus using familiar harnesses like Claude Code, Codex, or Pi. Across our internal malware analysis-focused benchmark, IDA MCPâs âcode modeâ architecture reduced token consumption by approximately 20% compared to popular alternatives.

-
đ 3Blue1Brown (YouTube) The Phone Number puzzle rss
Part of a monthly series of puzzles: https://momath.org/mindbenders/
-
đ pydantic/monty v1.0.0 - 2026-09-25 release
What's Changed
- Drop the experimental framing ahead of v1 by @rewitt94 in #811
- Percent formatting:
'...' % fooandb'...' % barby @samuelcolvin in #825 - fix flakey js test by @samuelcolvin in #826
- Implement the
format()builtin by @samuelcolvin in #827 - Answer
date.today()/datetime.now()in standard execution by @rewitt94 in #812 - Implement remaining math aggregation functions and
fmaby @samuelcolvin in #831 - fix telemetry context propagation by @davidhewitt in #830
- Support
print(file=sys.stderr)by @rewitt94 in #818 - Finish
binasciiwith the uu, quoted-printable and CRC-16 conversions by @rewitt94 in #816 - Build Python 3.14t wheels by @Kludex in #832
- make it possible to run smoke test from npm by @davidhewitt in #619
- fix browser smoke test install by @samuelcolvin in #844
- Move examples-only Python deps out of the dev group by @samuelcolvin in #849
- Match CPython for int/float arithmetic on long ints and float pow overflow by @samuelcolvin in #834
- Make the
examplesdependency group opt-in by @samuelcolvin in #852 - CWD support by @samuelcolvin in #828
- Add a watchdog for the CPython side of test cases and deflake
lookup__mutation.pyby @samuelcolvin in #854 - Accept long ints across
pow(),round()and themathmodule by @samuelcolvin in #853 - Eager awaits by @samuelcolvin in #833
- Better
casefoldand other case methods compatibility by @samuelcolvin in #857 - Deps update by @samuelcolvin in #859
- Add the remaining
itertoolscallables barteeby @samuelcolvin in #860 - Revert itertools by @samuelcolvin in #861
- Support runtime definition of types like
tuple[int, str]anddict | dictby @samuelcolvin in #858 - Implement the
sysconstants with a known value by @samuelcolvin in #863 - Support attribute and subscript targets in unpacking by @samuelcolvin in #865
- Add the antigravity example, running Monty in the browser by @samuelcolvin in #864
- Fix re-entrant source windows in
chain,pairwiseandaccumulateby @rewitt94 in #843 - Missing
itertoolsmethods by @samuelcolvin in #862 - Add a regression test for the trailing-slash symlink escape by @samuelcolvin in #868
- Add the
randommodule and anos.urandomhost call by @samuelcolvin in #867 - Add the
copymodule by @rewitt94 in #791 - Check container growth against the memory limit before it allocates by @rewitt94 in #845
- Follow-ups for
datetimeby @rewitt94 in #838 - Fix two container-reentrancy panics by @rewitt94 in #846
- don't make static strings a requirement for dump stability by @davidhewitt in #810
- establish tampered snapshots as permitted: garbage in garbage out by @davidhewitt in #873
- Fix async task scheduling for batched host failures by @flyingmutant in #875
- Give each callable type its own
PyTrait::py_callby @rewitt94 in #878 - Flat layout for wire value type by @samuelcolvin in #871
- Serialize dumps after the header in place, without a second buffer by @samuelcolvin in #881
- Shrink the largest dag decode bench to fit the decode budget by @samuelcolvin in #883
- remove needless
SOFT_LIMITvariable frommonty-allocby @davidhewitt in #763 - Add
time.time(),time.sleep()andasyncio.sleep()by @samuelcolvin in #866 - expose otel trace context from snapshots by @davidhewitt in #885
- purge
pool-architecture.mdby @davidhewitt in #887 - Replace session
max_durationwith feed and turn limits by @rewitt94 in #880 - add a general solution for wire decoding budgets by @davidhewitt in #874
- try to hide object internals by @davidhewitt in #886
- fix merge conflict by @davidhewitt in #891
- Comment and documentation fixes by @rewitt94 in #888
- Sort
StaticStringsalphabetically by @rewitt94 in #889 - Change
DumpErrorfor future compatibility by @rewitt94 in #890 - Implement
eval(),exec()andlocals()by @samuelcolvin in #855 - Auto OS calls by @samuelcolvin in #892
- Use sleep and random in the antigravity example by @samuelcolvin in #897
- Timezone support by @samuelcolvin in #898
- Poll the time limit in large three-argument pow by @samuelcolvin in #899
- Reject overlapping host directories at mount registration by @samuelcolvin in #741
- Extend
timemodule by @samuelcolvin in #901 - Replace wire types with whitelist of allowed types, and proxy for everything else by @samuelcolvin in #900
- Rename
AutoOsCallstoOsPolicyby @samuelcolvin in #902 - Resolve exception classes directly and model them inbound by @samuelcolvin in #905
- Describe Monty as a sandbox and name the open-source form "OSS Monty" by @samuelcolvin in #904
- Uprev
ruff, pre-check source by @samuelcolvin in #906 - Bump to v1.0.0-beta.1 by @samuelcolvin in #903
- bump to b2, fix
monty-proto's self dev-dependency by @samuelcolvin in #908 - Switch dumps to use
CBORinstead ofpostcardby @samuelcolvin in #913 - Give hot dumped types one-letter serde names by @samuelcolvin in #922
- Report the source position of every suspension by @samuelcolvin in #909
- Bump version to v1.0.0-beta.3 by @samuelcolvin in #927
- Classify a heap type for
copyin one place by @rewitt94 in #894 - Move builtin class attribute lookup into
Type::class_getattrby @rewitt94 in #910 - Round-trip
BuiltinFunctionthrough napi by @rewitt94 in #911 - Docs fixes by @samuelcolvin in #929
- Fix compile-time panic for lambda with nested comprehension by @rewitt94 in #925
- Build the type checker lazily, charging it to the worker baseline by @samuelcolvin in #928
- Add session IDs, forking, ephemeral sessions and auto-resume by @samuelcolvin in #933
- reduce gap between browser WASM and Node implementations by @davidhewitt in #665
- Prepare v1.0.0 đ by @samuelcolvin in #936
New Contributors
- @Kludex made their first contribution in #832
- @flyingmutant made their first contribution in #875
Full Changelog :
v0.0.23...v1.0.0 -
đ HexRaysSA/plugin-repository commits sync repo: +1 plugin, +5 releases rss
sync repo: +1 plugin, +5 releases ## New plugins - [patching-ng](https://github.com/mahmoudimus/patching) (0.3.0) ## New releases - [augur](https://github.com/0xdea/augur): 0.10.2 - [ida-mcp](https://github.com/hexrayssa/ida-mcp): 20260924.0.3, 20260924.0.2, 20260924.0.1 -
đ Filip Filmar Fuchsia Internals, Vol. II: The Build System and Toolchains rss
Volume II of the Fuchsia Internals series takes on the part of the system that is widely regarded as opaque on first contact: the build. The opacity has one principal cause: the multi-toolchain model, in which a single source target may be compiled many times, once per toolchain context, each producing distinct outputs. Once that idea clicks, the rest follows. The full PDF is at the bottom.
A caveat on these reports: they are auto-generated, so take the specifics with a grain of salt. In my own reading they hold up well and read as generally correct, but verify against the source before you rely on any one detail.
-
đ New Music Releases Philip Glass - The Hours (from "The Hours") rss
Philip Glass - a new release is available:
- 2026-09-25: The Hours (from "The Hours") (Single)
Amazon: Canada | Deutschland | France | United Kingdom | United States
Visit muspy for more information.
-
đ New Music Releases Max Richter - While the Flood Rages rss
Max Richter - a new release is available:
- 2026-09-25: While the Flood Rages (Single)
Amazon: Canada | Deutschland | France | United Kingdom | United States
Visit muspy for more information.
-
đ New Music Releases Paul Oakenfold - Big Brother Theme 2026 rss
Paul Oakenfold - a new release is available:
- 2026-09-25: Big Brother Theme 2026 (Single)
Amazon: Canada | Deutschland | France | United Kingdom | United States
Visit muspy for more information.
-
đ New Music Releases Linkin Park - Unshatter Film Soundtrack (Live in SĂŁo Paulo) rss
Linkin Park - a new release is available:
- 2026-09-25: Unshatter Film Soundtrack (Live in SĂŁo Paulo) (Soundtrack)
Amazon: Canada | Deutschland | France | United Kingdom | United States
Visit muspy for more information.
-
đ New Music Releases Armin van Buuren - Take Me Home rss
Armin van Buuren - a new release is available:
- 2026-09-25: Take Me Home (Single)
Amazon: Canada | Deutschland | France | United Kingdom | United States
Visit muspy for more information.
-
đ New Music Releases Faithless - We Come 1 rss
Faithless - a new release is available:
- 2026-09-25: We Come 1 (Single)
Amazon: Canada | Deutschland | France | United Kingdom | United States
Visit muspy for more information.
-
đ New Music Releases Kaskade - ORIGIN // rss
Kaskade - a new release is available:
- 2026-09-25: ORIGIN // (Album)
Amazon: Canada | Deutschland | France | United Kingdom | United States
Visit muspy for more information.
-
đ New Music Releases The Ocean - Solaris rss
The Ocean - a new release is available:
- 2026-09-25: Solaris (Album)
Amazon: Canada | Deutschland | France | United Kingdom | United States
Visit muspy for more information.
-
đ Ampcode News Less Noise rss
Amp now collapses your agent's step-by-step work even more, since you care about what the agent did, not how it got there. (You can still expand the steps when needed.)
A year ago, watching your agent's every step sometimes helped, and you probably were only running one agent.
Now? You have lots of agents. You should be giving them harder, deeper work and verification demands. They should run for longer on their own, without you watching them from up close. If you have the patience to watch your agents work, you're giving them too short a leash.

-

