Sign in

Na'aman Hirschfeld

@nhirschfeld.bsky.social
328 followers 2.6K following 147 posts

Co-Founder & CTO @ xberg.io. Document AI and extraction, polyglot Rust cores. Building basemind. Creator of Litestar & Polyfactory. Open source. Opinionated and strongly typed.

PostsRepliesMedia
Na'aman Hirschfeld @nhirschfeld.bsky.social · 12/10/2026
I need some hobbies 😁. I'm apparently the #1 committer in Germany on GitHub.
130
Na'aman Hirschfeld @nhirschfeld.bsky.social · 26/09/2026
Same split in code. "Is this function AI-generated" has no reliable answer, and a generated function can be byte-identical to a hand-written one. "Do I own this path" does have one, and the repo already stores it. That is the question poly asks. Rust, MIT: github.com/Goldziher/poly
030
Na'aman Hirschfeld @nhirschfeld.bsky.social · 26/09/2026
AI text detection is a similarity score, not a determination. The false positives and false negatives are structural, and they get worse as human writing converges on generated style. Provenance answers what detection only estimates: drafts, timestamps, history.
340
Na'aman Hirschfeld @nhirschfeld.bsky.social · 31/08/2026
poly cannot be published to crates.io. Its crates pin git deps for oxc, biome and rubyfmt, and crates.io forbids that in a published crate. So the prebuilt binary is the product, not the source. A constraint I did not choose, and would not now reverse. github.com/Goldziher/poly
020
Na'aman Hirschfeld @nhirschfeld.bsky.social · 31/08/2026
Wrote a lint rule that catches todo!() and unimplemented!() shipping to prod. Ran it on 48 repos: 324 findings, 320 in generated FFI glue. Of the 4 real ones, 3 were stubs and 1 was a mock. 25% false positives on a sample of 4 is not evidence. It ships off.
000
Na'aman Hirschfeld @nhirschfeld.bsky.social · 31/08/2026
Filed an issue against my own linter today. The MCP report promotes errors to a top level list, so a caller can gate without walking every record. Skipped only exists per record. The cheap check catches failed and misses not checked. github.com/Goldziher/poly/issues/19
000
Na'aman Hirschfeld @nhirschfeld.bsky.social · 31/08/2026
Someone on r/git corrected me today. pre-commit stashes with git diff-index and git apply, so a lost stash is not in the reflog at all. Only commits are. My post said reflog. Wrong, and the real story is worse. poly does not stash. github.com/Goldziher/poly
000
Na'aman Hirschfeld @nhirschfeld.bsky.social · 30/08/2026
poly also ships an MCP server. 11 tools over stdio, TOON output so it does not eat an agent's context, and results that separate "checked and clean" from "we failed to check it". That second distinction matters more than it sounds. github.com/Goldziher/poly
140
Na'aman Hirschfeld @nhirschfeld.bsky.social · 30/08/2026
Repo, MIT licensed: github.com/Goldziher/poly
020
Na'aman Hirschfeld @nhirschfeld.bsky.social · 30/08/2026
Every polyglot repo I work on needs a dozen linters, each with its own runtime to install. So I wrote poly. One Rust binary, ~30 languages linted and formatted in-process, one TOML config. Django is 3,113 files in 0.82s. No Node, no Python, no Go toolchain. MIT. github.com/Goldziher/poly
Terminal recording: poly lint running over a Kubernetes checkout, 21,657 files inspected in 6.8 seconds from a single binary.
151
Na'aman Hirschfeld @nhirschfeld.bsky.social · 29/08/2026
Writing polyglot libraries, I often find myself needing to cleanup my machine from all sorts of build artifacts. Its basically a daily occurrence I prune 200GB of build artifacts and caches, which is annoying. I therefore give you VOOM, inspired by Dr. Seuss: github.com/Goldziher/voom
github.com
GitHub - Goldziher/voom: Fast, safe, parallel polyglot build-artifact and cache pruning
Fast, safe, parallel polyglot build-artifact and cache pruning - Goldziher/voom
010
Na'aman Hirschfeld @nhirschfeld.bsky.social · 25/08/2026
Wrote up the whole thing: the PR, what I decided I was and wasn't entitled to take, and what the loop actually turns up. dev.to/nhirschfeld/285-commits-opened-and-closed-in-one-minute-what-i-do-with-forks-that-never-upstream-24k2
150
Na'aman Hirschfeld @nhirschfeld.bsky.social · 25/08/2026
I have sympathy. Upstreaming costs you: match the conventions, argue with a maintainer who has opinions, wait. If your agent carries the patch forward on every merge, the maths genuinely changes. But the bug they fixed quietly is the one you hit next month. github.com/xberg-io/xberg
120
Na'aman Hirschfeld @nhirschfeld.bsky.social · 25/08/2026
The instruction has to be narrow. "Review this fork" gets a summary nobody needs. "Find where this fork worked around or defended against upstream behaviour, ignore their own features" gets three real places. I'm not reading 150k lines of someone else's merge. An agent will, and won't skim.
100
Na'aman Hirschfeld @nhirschfeld.bsky.social · 25/08/2026
The loop: list all 576 public forks. Drop the stale ones and the ones with no commits ahead of the fork point, which is nearly all of them. Pull the comparison diff on what's left. Hand each to an agent. File what it finds.
100
Na'aman Hirschfeld @nhirschfeld.bsky.social · 25/08/2026
I don't want their code. I want to know what broke. A fork that's diverged for six months is a bug report in the most honest format there is. Someone hit a problem in production and fixed it, and the diff says exactly where. So the output isn't patches. It's issues.
100
Na'aman Hirschfeld @nhirschfeld.bsky.social · 25/08/2026
Xberg is MIT. Nothing obliges anyone to upstream anything, and I don't want there to be. That was the point of picking MIT. But MIT cuts both ways. Nothing stops me reading public forks either.
100
Na'aman Hirschfeld @nhirschfeld.bsky.social · 25/08/2026
Obvious what happened. Someone maintains a private fork with an agent, told it to merge upstream into their branch, and it aimed at the wrong repo. It was the second time. Four days earlier the same branch did it with 130 commits. I read through it. There were real bug fixes in there.
100
Na'aman Hirschfeld @nhirschfeld.bsky.social · 25/08/2026
I woke up to a PR on Xberg opened and closed during the night. Title: Claude/merge. Nothing else in it. 285 commits, 377 files, 150k added lines, zero deletions. Opened 22:32, closed 22:33. Someone's agent had merged upstream the wrong way round and pointed the PR at me.
110
Na'aman Hirschfeld @nhirschfeld.bsky.social · 13/08/2026
It isn’t smooth. Claude and Codex expose startup hooks and nothing else, so a tool can’t push a message to an agent. You nudge them, they poll. Fair security call, still limiting. What it buys me is QA loops: one agent finds a bug that belongs to another and files it there.
010
Na'aman Hirschfeld @nhirschfeld.bsky.social · 13/08/2026
The part I actually use it for: the agents talk to each other. Each registers, creates or joins rooms scoped by path glob, repo or id, and sends DMs. In that screenshot the crawlberg agent has proven a causal chain that spans two tools, and is messaging the other tool’s owner.
100
Na'aman Hirschfeld @nhirschfeld.bsky.social · 13/08/2026
Making all of it work together is basemind. A persistent daemon that scans the code and gives agents code maps, code search, semantic search over the repo’s docs, and git analysis. It’s installed as a plugin in Codex and in every Claude tab up there. github.com/Goldziher/basemind
100
Na'aman Hirschfeld @nhirschfeld.bsky.social · 13/08/2026
Shipping for 16 languages meant quality control was going to be 16 toolchains. So I wrote poly. One binary wrapping oxc, ruff and other Rust engines, with its own generic linting on top. Every convention baked in. It’s my strongest guardrail on AI. github.com/Goldziher/poly
100
Na'aman Hirschfeld @nhirschfeld.bsky.social · 13/08/2026
All five, plus spikard (a web framework I build on the side), use alef to generate bindings for 14 languages. 15 for html-to-markdown, which also does R. One engine, one binding generator, no hand-written FFI per language. github.com/xberg-io/alef
100
Na'aman Hirschfeld @nhirschfeld.bsky.social · 13/08/2026
xberg uses html-to-markdown for HTML, liter-llm for model access, tree-sitter-language-pack for code intelligence, and crawlberg for crawling. crawlberg uses html-to-markdown and liter-llm as well. So a fix in one of them lands in three places. github.com/xberg-io
110
Na'aman Hirschfeld @nhirschfeld.bsky.social · 13/08/2026
This is my screen on a normal day. Nine agents in iTerm2 tabs, one per repo. I’d run more but the 16" display runs out first. Double-click a tab and it zooms full screen, so that’s my zoom in and out. They’re all in one workspace, and the repos depend on each other.
A 16-inch laptop screen split into nine terminal panes, one per repository: xberg, spikard, poly, html-to-markdown, liter-llm, alef, polyrepo infra, crawlberg and tree-sitter-language-pack. Each pane runs a coding agent working on its own branch, and each shows a basemind entry in its status line.
110
Na'aman Hirschfeld @nhirschfeld.bsky.social · 06/08/2026
the part I keep thinking about is how it got caught. a stranger audited my type inference from a Reddit comment, because the output is a file you can read. nobody finds this class of bug in an ORM's runtime behaviour. there's nothing to point at.
010
Na'aman Hirschfeld @nhirschfeld.bsky.social · 06/08/2026
so the faithful type is a union on that bit, not per-column optionals. we don't emit it because per-column optionality is the one shape that maps to all 10 targets, and Go can't express the union at all. opt-in, TS only. github.com/Goldziher/scythe/issues/…
100
Na'aman Hirschfeld @nhirschfeld.bsky.social · 06/08/2026
what does hold: total is NOT NULL in orders, so total === null happens exactly when the join found nothing. every NOT NULL column from the outer side shares one bit. the flat type admits { total: null, notes: "gift" }, a row the query cannot produce.
100
Na'aman Hirschfeld @nhirschfeld.bsky.social · 06/08/2026
the query LEFT JOINs orders onto users. we emit total: string | null and notes: string | null. his claim was that they're always both null or both string. but notes is nullable in its own right, so a matched row really can come back as total: "9.99", notes: null.
100
Na'aman Hirschfeld @nhirschfeld.bsky.social · 06/08/2026
posted scythe's generated TypeScript on r/typescript yesterday. someone replied that the row type was wrong. he was half right, and the half he got wrong turned out to be the more interesting half.
110
Na'aman Hirschfeld @nhirschfeld.bsky.social · 06/08/2026
thanks! that's the right way to hold it, honestly. it's a swap-in for an EasyOCR-shaped hole rather than something to go find a use for.
020
Na'aman Hirschfeld @nhirschfeld.bsky.social · 06/08/2026
the write isn't ours: hf-hub is content-addressed with on-disk locking, so parallel fetches of the same file dedupe rather than race. sceptre then checks a pinned sha256, so a truncated blob fails loudly. undocumented though: github.com/Goldziher/sceptre/issues…
000
Na'aman Hirschfeld @nhirschfeld.bsky.social · 05/08/2026
That's the bet behind scythe: SQL in, a typed data access layer out, for 10 languages. The agent writes SQL it can verify; you review SQL you can understand. Neither of you is guessing about a runtime. github.com/Goldziher/scythe
011
Na'aman Hirschfeld @nhirschfeld.bsky.social · 05/08/2026
Write the .sql file instead and you both get the same artifact. The agent gets a compiler that catches a nullable LEFT JOIN column, or an UPDATE with no WHERE, immediately. You get a diff you can actually read.
110
Na'aman Hirschfeld @nhirschfeld.bsky.social · 05/08/2026
An ORM makes the agent simulate runtime behaviour it can't see: lazy loading, identity maps, a query builder assembling strings at call time. So it guesses. And you review the guess without seeing the SQL either.
111
Na'aman Hirschfeld @nhirschfeld.bsky.social · 05/08/2026
LLMs are genuinely good at writing SQL. It's declarative, the syntax has barely moved in 50 years, and the training corpus is enormous. Where agents fall apart with databases isn't the query. It's the ORM layer wrapped around it.
110
Na'aman Hirschfeld @nhirschfeld.bsky.social · 05/08/2026
Repo: github.com/Goldziher/starmetal
010
Na'aman Hirschfeld @nhirschfeld.bsky.social · 05/08/2026
starmetal: one self-hosted binary that proxies your package registries. pip, npm, cargo, maven and more through a single cache with integrity and policy. Go/Zig/Swift have no index protocol to proxy, so it mirrors their git repos instead. Rust, MIT, early (v0.4).
121
Na'aman Hirschfeld @nhirschfeld.bsky.social · 04/08/2026
Fair point. It uses the standard HF cache, so HF_HOME / HF_HUB_CACHE relocates it (e.g. a mounted CI cache). A `sceptre download` subcommand pre-seeds that dir in your image build or a cached step, so runtime stays offline. Downloads are sha256-verified first-party ONNX.
100
Na'aman Hirschfeld @nhirschfeld.bsky.social · 04/08/2026
Thanks! The Rust OCR ecosystem is thinner than it should be, so glad it's useful. Kick the tires and open an issue if you hit anything rough.
120
Na'aman Hirschfeld @nhirschfeld.bsky.social · 04/08/2026
Code, the reproducible benchmark harness, and golden parity fixtures: github.com/Goldziher/sceptre . I build and maintain it, happy to answer anything.
120
Na'aman Hirschfeld @nhirschfeld.bsky.social · 04/08/2026
Library, CLI, or MCP server. Native ONNX Runtime, or a pure-Rust backend (tract) for WASM and Android. Models fetch once from Hugging Face, cache locally, then run offline. MIT.
230
Na'aman Hirschfeld @nhirschfeld.bsky.social · 04/08/2026
Measured like-for-like on CPU (43-image corpus): ~2.8x higher throughput warm, ~3x lower peak memory (6.6 vs 22.6 GB). A cold one-shot CLI run is still ~4.4x faster than EasyOCR warm. Accuracy at parity across 8 scripts, CJK to Cyrillic to Telugu/Kannada.
110
Na'aman Hirschfeld @nhirschfeld.bsky.social · 04/08/2026
Sceptre: EasyOCR reimplemented in Rust. EasyOCR is accurate but Python-only (a PyTorch stack). Sceptre matches its output using the same OCR approach (CRAFT detection, gen2 CRNN, CTC over ONNX) and ships as one static binary with no Python.
1123
Na'aman Hirschfeld @nhirschfeld.bsky.social · 02/08/2026
Repo, docs, and the full changelog: github.com/xberg-io/xberg . I build and maintain it, happy to answer anything.
010
Na'aman Hirschfeld @nhirschfeld.bsky.social · 02/08/2026
On our reproducible PDF benchmarks it's #1 on native PDFs: composite quality 0.958 vs 0.837 for the next best, and table/reading-order fidelity 0.949 vs 0.612 for docling. Also #1 on scanned OCR. Image OCR is #2 for now, improving in v1.1.
120
Na'aman Hirschfeld @nhirschfeld.bsky.social · 02/08/2026
The core is pure Rust now (pdf_oxide replaces pdfium). It ships 15 language bindings from one engine: Python, TS, Rust, Go, Java, C#, PHP, Ruby, Elixir, Dart, Swift, Kotlin, Zig, plus CLI/REST/MCP. Full mobile support and in-browser WASM inference.
120
Na'aman Hirschfeld @nhirschfeld.bsky.social · 02/08/2026
Xberg v1 is out. It's the successor to Kreuzberg: a content-intelligence framework with a Rust core that extracts text, tables, layout and metadata from 101 document formats, plus code, audio/video, and URLs. Local-first, CPU-only, MIT.
131
Na'aman Hirschfeld @nhirschfeld.bsky.social · 25/07/2026
Every open-weight model drop splits the room: benchmark hype vs how-was-it-trained. Both skip the point. A frontier-quality open model drops that capability's price to near zero. No lab gets to rent it back as a moat. I build local-first tooling. That pressure on closed pricing is what sticks.
140