[{"content":"It was 2 AM. I was digging through my local anime folder trying to figure out why my media server hadn\u0026rsquo;t picked up a new episode. I opened the SQLite database file out of curiosity — just to see what was in there.\nI could read it. All of it. Every title. Every rating. Every entry in my watch history. Plain UTF-8 text, sitting unprotected on my disk.\nI closed the terminal and stared at the ceiling for a while.\nThat\u0026rsquo;s it. That\u0026rsquo;s the whole story of why I\u0026rsquo;m building this.\nThe State of Self-Hosted Media You have Plex, which started as a community project and slowly turned into a product that asks you to log into their servers just to watch your files on your machine. You have Jellyfin — genuinely good, community-driven — but built in C#, with an architecture that makes some things harder than they need to be. You have Navidrome for music, Kavita for books, Komga for manga. Great tools, all of them. But every single one is a silo.\nYou watch anime on one app. Read manga on another. Listen to music on a third. Each one with its own database, its own auth system, its own idea of what your library looks like. And none of them take security seriously at the level I think it deserves.\nNone of them encrypt your metadata by default.\nThink about that for a second. Your watch history. Your ratings. The titles of everything you\u0026rsquo;ve ever consumed. Sitting in a plaintext SQLite file on your disk. If someone steals your drive — or just has a few minutes unsupervised near your PC — they get a complete picture of your media consumption with zero effort.\nThat bothers me.\nWhat I\u0026rsquo;m Building Psyche is a local-first, high-performance, high-security media server written in Rust.\nAnime. Manga. TV shows. Books. Music. One server. One API. One frontend.\nThe name comes from Serial Experiments Lain (1998). In SEL, the Psyche is the self — private, local, encrypted. The boundary between what is yours and what belongs to the network. That\u0026rsquo;s exactly what this server is. Your data. Your machine. Nobody else\u0026rsquo;s business.\nBut more than that — it\u0026rsquo;s built around the idea that your data is yours and nobody else\u0026rsquo;s business. Your watch history is encrypted on disk. Your metadata is encrypted on disk. Your thumbnails are encrypted on disk. An attacker who gets filesystem access gets ciphertext. The server decrypts and re-encrypts in real time; plaintext never touches disk.\nAnd when I say local-first, I mean it in the paranoid sense. Every remote feature — AniList sync, metadata APIs, LLM translation, lyrics lookups — has a local alternative. You can run Psyche with zero outbound connections and lose nothing but convenience. You can import the Kitsu database dump (the dev team shares it freely) and have full anime metadata offline forever.\nThe Stack (and Why It Matters) Rust. Axum. SQLite via sqlx. That\u0026rsquo;s the core.\nI chose Rust because I needed the compiler to be annoying. Seriously. When you\u0026rsquo;re building something that handles encrypted user data, streams media, and walks a filesystem, you want a compiler that says \u0026ldquo;no\u0026rdquo; a lot. You want to know at compile time that you haven\u0026rsquo;t introduced a buffer overflow, a data race, or a null dereference. The Rust compiler is extremely verbose and boring — and that\u0026rsquo;s exactly what you want when security is the core value proposition.\nThe database is SQLite. Not Postgres. Not a document store. SQLite, because this is a local-first server and I refuse to make you run a database daemon just to watch your own anime. Async-native via sqlx, with compile-time query checking — the SQL is verified against the actual schema at compile time. No magic, no ORM hiding queries from you. Plain SQL migrations in numbered .sql files. Fully auditable.\nThe HTTP layer is Axum — Tower-native, composable, maintained by the Tokio team. No surprises.\nThe First-Party Ecosystem This isn\u0026rsquo;t just a server. It\u0026rsquo;s the beginning of an ecosystem. In SEL, Lain\u0026rsquo;s NAVI is the hardware and software she uses to connect to the Wired. Each crate in this project is a layer — a piece of the stack that lets Psyche do what it does.\nNAVI: She does an insane upgrade to her NAVI — and that\u0026rsquo;s the energy I\u0026rsquo;m bringing to this codebase: You already know zantetsu — my anime filename parser that I wrote about a few months ago. It\u0026rsquo;s Layer 00. The core of Psyche\u0026rsquo;s scanner. It handles the chaos: [SubsPlease] Jujutsu Kaisen - 24 (1080p) [A1B2C3D4].mkv, One Punch Man S02E03 1080p WEBRip x264.mkv, all of it. 92%+ accuracy on the heuristic parser alone, with a DistilBERT + CRF neural fallback for the really weird ones.\nBut I\u0026rsquo;m building two more crates that don\u0026rsquo;t exist yet:\npsyche-translate (Layer 01) is a translation engine with a pluggable provider model. Remote LLM providers (OpenAI, Anthropic, Gemini), local providers via Ollama or llama.cpp, or nothing at all. The crate maintains episode context across segments so character names stay consistent. You can translate an entire subtitle track, a manga page, or a book chapter with one call — and the provider is just configuration.\nfill-metadata (Layer 02) is the ambitious one. It\u0026rsquo;s a machine learning pipeline that analyzes raw media files and infers metadata when none exists. Video scene analysis, audio fingerprinting, speech-to-text for title inference. If you have a folder full of unnamed video files from years ago, fill-metadata proposes what they are. You review, you approve, it commits to the database. No magic. No automatic commits. You\u0026rsquo;re always in control.\nThe OCR Pipeline Here\u0026rsquo;s where it gets interesting.\nPGS subtitles — the bitmap-based subtitle format used on Blu-rays — are images. You can\u0026rsquo;t search them, translate them, or copy text from them. Psyche will OCR them. Extract the text. Make it searchable.\nBut more than that: manga pages and book pages get the full treatment. OCR extracts text regions with bounding boxes. psyche-translate translates each region. A rendering pass reconstructs the image — translated text is typeset into the original bounding region, the original text is inpainted out, and you get a page that looks like the original but reads in your language.\nDifferent OCR models for different domains. Manga panels and speech bubbles are not the same problem as a scanned book page. Treating them as the same problem is how you get bad results.\nThis is not a weekend hack. This is a pipeline I\u0026rsquo;m going to build correctly, one crate at a time.\nThe Integrations Sonarr. Radarr. Prowlarr. Lidarr. Readarr. Jackett. Discord rich presence. Discord bot. Webhooks. AniList sync. Spotify playlist import. Torrent streaming with sequential piece prioritization. All of it.\nBut — and this is important — all of it is opt-in. Every single integration is disabled by default. You enable what you want. And for every remote integration, there\u0026rsquo;s a local alternative. The arr stack is already local. Jackett is already local. For everything else: Kitsu offline dump, local Ollama, local Jackett, NFO sidecar files.\nThe paranoid user path is fully supported. Not as an afterthought. As a design requirement.\nPlugins and the Wired Here\u0026rsquo;s where the SEL metaphor goes from aesthetic to structural.\nThe plugin system is sandboxed. Third parties can add metadata providers, scanner strategies, translation backends, export formats — without forking, without touching the core. The plugin boundary is explicit and enforced. What runs inside the plugin cannot reach outside it without going through a defined interface. That\u0026rsquo;s not just good architecture. That\u0026rsquo;s the only architecture I\u0026rsquo;ll accept for a server that holds your encrypted data.\nAnd further out — there\u0026rsquo;s the Wired.\nIn SEL, the Wired is the network that connects all consciousness. The place where the boundary between self and other dissolves — but only if you choose to enter. That\u0026rsquo;s the opt-in P2P community layer. Activity feeds. Ratings. Shared lists. Decentralised — no central server controls anything. You enable it by explicitly accepting the terms. Default state: fully off. No data leaves your machine unless you say so.\nI genuinely don\u0026rsquo;t know yet exactly how to build the Wired. Distributed systems at that level are outside my current expertise — which is part of why I\u0026rsquo;m writing this post. But I know what it needs to feel like: you are always Lain, connecting on your own terms. The network does not consume you. You reach into it and pull out what you want.\nThe monetization model is symbolic and optional. A store for cosmetic rewards — icons, badges, emoji packs, Discord roles. Discord-style: you pay for identity, never for features. Revenue goes to keeping me focused on building this instead of looking for other ways to pay rent.\nAndroid app. Desktop app via Tauri. Web-first, native apps when the web experience is stable.\nWhy GPL v3 I chose the GNU General Public License version 3 deliberately.\nGPL v3 is the strongest copyleft license available. If someone forks Psyche and ships it — commercially or otherwise — they must release the full source under the same terms. You cannot take Psyche, close the source, and sell it. There is no enterprise exception path. There is no \u0026ldquo;dual licensing\u0026rdquo; trick. The community built it, the community keeps it.\nI\u0026rsquo;ve watched too many self-hosted projects get acqui-hired or slowly enshittified. Jellyfin exists because Emby went closed-source. This won\u0026rsquo;t happen to Psyche. The license makes it structurally impossible.\nThe Current State I\u0026rsquo;m at Phase 0. The Cargo workspace is bootstrapped. The React frontend template exists (it\u0026rsquo;s beautiful, by the way). The docs are written: vision, architecture, security model, encryption contract.\nPhase 1 is next: auth system, filesystem scanner, anime API, streaming, serving the frontend as a single binary. Each slice is tested before the next begins. TDD, no exceptions. One ridge at a time.\nThis is going to take years to reach the full vision. I know that. I\u0026rsquo;m not going to pretend otherwise.\nBut the foundation is being built correctly. The architecture decisions are documented. The security model is written before the first line of production code. The encryption contract specifies exactly what is encrypted, what is not, and why.\nThat\u0026rsquo;s how you build something that lasts.\nYou Read This Far. That Means Something. Seriously. Most people bounced at the first paragraph. You didn\u0026rsquo;t.\nThat tells me you\u0026rsquo;re the kind of person who actually thinks about this stuff. Who notices when something is wrong and wonders if it could be better. Who got a little bothered by the idea of a plaintext file on disk cataloguing everything you\u0026rsquo;ve ever watched. Who looked at the fragmentation of self-hosted media and thought \u0026ldquo;someone should fix this.\u0026rdquo;\nThat person is exactly who I\u0026rsquo;m looking for. And I mean that in the broadest possible sense.\nI\u0026rsquo;m a technical guy. I can write Rust, design a database schema, wire up an API. I have experience with machine learning, mobile development, and P2P systems. But I have genuine blind spots — and this project needs more than just people who can code.\nI have no experience building communities. None. I don\u0026rsquo;t know how to grow a Discord server, how to write announcements that make people feel included, how to create a culture around a project. If you\u0026rsquo;ve done that — for any project, any community, anything — that knowledge is worth more to Psyche right now than another Rust PR.\nI have experience with UI/UX enough to be functional. The frontend template I built looks clean, but I’m not a full time designer. I don’t know how a reader should feel flipping through manga pages. I don’t know what the perfect music player experience feels like. If you care about that — if you’ve ever redesigned something and made it feel right — there is a blank canvas here with your name on it.\nI have no deep expertise in advanced cryptography. I know enough to design the AES-256-GCM pipeline, choose Argon2id, and write a threat model. But the people who can look at my key derivation scheme and find the subtle flaw I missed — those people don\u0026rsquo;t exist in my network yet.\nI have no experience with advanced pentesting. I can write secure code and think about threat models. But the people who attack systems for a living, who find the edge cases I didn\u0026rsquo;t even know existed, who can tell me exactly where my security assumptions break — I need those people.\nI have no languages beyond English and Portuguese. This thing is going to need to speak every language eventually — not just in the frontend, but in documentation, community, and support. If you speak another language and care about this project, there is space for you here.\nI don\u0026rsquo;t have a team. I don\u0026rsquo;t have funding. I don\u0026rsquo;t have a company behind this. I have a vision, a compiler, and a GPL v3 license that guarantees it stays free forever.\nWhat I\u0026rsquo;m asking for is simple: if something in this post made you think \u0026ldquo;I could help with that\u0026rdquo; — reach out.\nNot just developers. Designers, writers, community builders, ML researchers, mobile devs, security researchers, people who are just really opinionated about what a good media library experience should feel like. Translators — this thing is going to need to speak every language eventually. People who just want to report bugs with good screenshots and logs. People who want to write documentation because they know what it feels like to be lost in a project with none.\nThe contribution guide is strict about code quality because security is the core. But there is no strict bar for caring. If you care, there is space for you here.\nThe codebase has an AGENTS.md file that explains how we work — it was written to be read by humans and AI agents alike, because I use AI tools and I expect contributors will too. The rule isn\u0026rsquo;t \u0026ldquo;no AI.\u0026rdquo; The rule is \u0026ldquo;you own every line.\u0026rdquo; Read it, understand it, and bring your whole self — tools included.\nWrite to enrell@proton.me. Tell me what you do, what you\u0026rsquo;d want to work on, or just that you read this and it resonated. That\u0026rsquo;s enough to start a conversation.\nOr open an issue. Or star the repository and show up when something moves you. There\u0026rsquo;s no commitment required to care about something.\nThe dream is ambitious. The mountain is tall. But I\u0026rsquo;ve been staring at the dirty anime filenames in my local folder for years, waiting for the right tool to exist.\nI got tired of waiting.\nSee you in the Wired.\n","permalink":"https://enrell.github.io/en/posts/psyche/","summary":"\u003cp\u003eIt was 2 AM. I was digging through my local anime folder trying to figure out why my media server hadn\u0026rsquo;t picked up a new episode. I opened the SQLite database file out of curiosity — just to see what was in there.\u003c/p\u003e\n\u003cp\u003eI could read it. All of it. Every title. Every rating. Every entry in my watch history. Plain UTF-8 text, sitting unprotected on my disk.\u003c/p\u003e\n\u003cp\u003eI closed the terminal and stared at the ceiling for a while.\u003c/p\u003e","title":"Psyche: The Media Server That Keeps Your Data Yours"},{"content":"It was a Saturday afternoon. I pressed the power button on my PC and\u0026hellip; nothing. Fans spun for a second, LEDs blinked, then dead. Then it tried again. One second, dead. One second, dead. An infinite boot loop with no display output, no beep codes, nothing.\nWhat followed was a week-long odyssey of diagnostics, wrong guesses, a new motherboard, a bricked BIOS, and — when every computer in my house was dead — an 11 PM coding session on my phone to patch an open-source tool that was never meant to run on Android.\nAnd I had no multimeter, no POST card, no debug screen. Just my eyes and a lot of determination.\nThe Diagnosis My first suspect was the CPU. No Ethernet LED means no POST, and no POST usually means the CPU isn\u0026rsquo;t initializing. My board was an X99 P4 — the most basic X99 board you can find. Paired with a Xeon E5-2670v3, it had been running fine for months.\nBut \u0026ldquo;fine for months\u0026rdquo; on a budget X99 board with a 12-core Xeon? That\u0026rsquo;s asking a lot from the VRM.\nMy second suspect was the PSU. If it couldn\u0026rsquo;t deliver enough current to the CPU rail, the board would power on but never reach POST. Fans spin, LEDs glow, but the processor starves.\nMy third suspect was the motherboard itself. Budget X99 boards have modest VRM designs. A 12-core Haswell-EP chip pulling 120W TDP through 4-phase VRMs? It was a matter of when, not if.\nThe answer turned out to be two of three: both the PSU and the motherboard were dead. The VRM had given up, and the PSU wasn\u0026rsquo;t delivering clean current anymore.\nNew Hardware, New Hope I bought replacement parts:\nPSU: Husky Sledger — solid, reliable, enough headroom Motherboard: MR9A-H — a step up from the P4 CPU: Xeon E5-2640v3 — just in case the old one was damaged too I assembled everything, pressed power, and\u0026hellip; it booted. BIOS screen. Memtest passed. I installed my OS and started using it.\nFor about 30 minutes.\nThe Turbo Boost Incident Here\u0026rsquo;s where I made my first mistake.\nThe E5-2640v3 has a base clock of 2.6 GHz and turbo up to 3.4 GHz. But on these Chinese X99 boards, turbo boost is often locked by default. In games, especially with my RX 6600, that extra clock matters for frame stability.\nSo I thought: \u0026ldquo;Let me flash a modified BIOS with turbo unlock. Easy. I\u0026rsquo;ve done this before.\u0026rdquo;\nI had a backup of my old BIOS — the one from the dead X99 P4. In the heat of the moment, I grabbed the wrong file. Instead of flashing the MR9A-H BIOS, I flashed the P4 BIOS onto the MR9A-H board.\nThe flash completed. I rebooted.\nDead. Again. Same 1-second loop.\nI had just bricked my second motherboard in one week.\nThe CH341A Arrives The only way to recover a bricked BIOS chip is to reflash it externally. I ordered a CH341A programmer — the cheap, ubiquitous SPI flasher that every hardware tinkerer eventually owns. It arrived the next day.\nBut here\u0026rsquo;s the thing: I had no computer to use it on.\nMy PC was dead. My mom\u0026rsquo;s laptop had literally died earlier that week — the screen went black mid-zombie-apocalypse-series and never came back. No other computer in sight. It was nighttime. No friend nearby with a desktop. No local repair shop that could handle SPI flashing.\nI looked at my phone. A modern Android device. USB-C. OTG support.\n\u0026ldquo;Wait. The CH341A is USB. My phone has USB. Can I just\u0026hellip; use my phone?\u0026rdquo;\nThe Android Problem Flashrom is the standard open-source tool for reading and writing flash chips. It supports the CH341A out of the box. Problem: it\u0026rsquo;s a Linux desktop tool. There\u0026rsquo;s no Android app for this.\nFirst instinct: use Termux. It\u0026rsquo;s a Linux terminal emulator for Android. I could compile flashrom from source.\n1 2 3 pkg install git meson ninja libusb git clone https://review.coreboot.org/flashrom.git cd flashrom I compiled it. It built. But when I tried to run it:\n$ ./build/flashrom -p ch341a_spi -V Initializing ch341a_spi programmer libusb: error [sysfs_get_device_list] opendir devices failed, errno=13 Couldn\u0026#39;t initialize libusb! Permission denied. On Android, SELinux blocks direct access to /dev/bus/usb/. Without root, libusb_init() fails because it tries to scan the USB bus at startup, and that scan is denied.\nThe only way to access USB hardware on non-rooted Android is through termux-usb, which can request permission from the Android system and hand you an already-opened File Descriptor (FD). But flashrom has no idea what to do with an FD. It expects to scan the USB bus itself.\nSo I had two options: wait for a real computer, or make flashrom understand Android.\nI chose the second.\nAdding Android FD Support to Flashrom The core insight was this: flashrom uses libusb to talk to the CH341A. The normal flow is:\nlibusb_init(NULL) — initialize the library (this scans /dev/bus/usb/) libusb_open_device_with_vid_pid() — find and open the CH341A by its USB IDs Communicate with the device Step 1 fails on Android because SELinux blocks the USB bus scan. Step 2 fails because it needs step 1.\nBut libusb has a function most people don\u0026rsquo;t know about: libusb_wrap_sys_device(). It takes an already-opened file descriptor and wraps it into a libusb_device_handle directly. No scanning. No VID/PID lookup. Just: \u0026ldquo;here\u0026rsquo;s an FD, give me a handle.\u0026rdquo;\nThe catch? You still need a valid libusb_context. And libusb_init() tries to scan devices. The solution is LIBUSB_OPTION_NO_DEVICE_DISCOVERY, introduced in libusb 1.0.24. It tells libusb: \u0026ldquo;initialize yourself, but don\u0026rsquo;t go poking around /dev/bus/usb/.\u0026rdquo;\nHere\u0026rsquo;s what I added to programmers/ch341a_spi.c:\n1 2 3 4 5 6 7 8 9 10 11 12 13 /* Android FD support: get FD from environment variable */ static int get_android_usb_fd(void) { const char *env_fd = getenv(\u0026#34;ANDROID_USB_FD\u0026#34;); if (env_fd) { int fd = atoi(env_fd); if (fd \u0026gt; 0) { msg_pdbg(\u0026#34;Using Android USB FD from env: %d\\n\u0026#34;, fd); return fd; } } return -1; } And in the init function, before any USB scanning happens:\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 static int ch341a_spi_init(const struct programmer_cfg *cfg) { int android_fd = -1; char *fd_param = extract_programmer_param_str(cfg, \u0026#34;fd\u0026#34;); if (fd_param) { android_fd = atoi(fd_param); free(fd_param); } if (android_fd \u0026lt;= 0) android_fd = get_android_usb_fd(); libusb_context *ctx = NULL; if (android_fd \u0026gt; 0) { /* Android FD mode: skip device scanning */ struct libusb_init_option opts = { .option = LIBUSB_OPTION_NO_DEVICE_DISCOVERY }; ret = libusb_init_context(\u0026amp;ctx, \u0026amp;opts, 1); if (ret \u0026lt; 0) { msg_perr(\u0026#34;Couldn\u0026#39;t initialize libusb context: %s\\n\u0026#34;, libusb_error_name(ret)); return -1; } /* Wrap the FD directly into a device handle */ ret = libusb_wrap_sys_device(ctx, (intptr_t)android_fd, \u0026amp;data-\u0026gt;handle); if (ret \u0026lt; 0) { msg_perr(\u0026#34;Failed to wrap USB device fd %d: %s\\n\u0026#34;, android_fd, libusb_error_name(ret)); libusb_exit(ctx); return -1; } msg_pdbg(\u0026#34;Successfully wrapped USB device from FD %d\\n\u0026#34;, android_fd); } else { /* Original path: scan by VID/PID (needs root on Android) */ ret = libusb_init(\u0026amp;ctx); // ... existing code ... } // ... rest of initialization ... } The full patch is about 130 lines. Not a lot of code, but it changes everything about how flashrom initializes on Android.\nTesting It Live I compiled the patched flashrom:\n1 2 meson setup build -Dtests=disabled -Dwerror=false -Dprogrammer=ch341a_spi ninja -C build Then I wrote a small C wrapper because termux-usb passes the FD as an argument to the child process:\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 #include \u0026lt;stdio.h\u0026gt; #include \u0026lt;stdlib.h\u0026gt; #include \u0026lt;unistd.h\u0026gt; int main(int argc, char **argv) { if (argc \u0026lt; 2) { fprintf(stderr, \u0026#34;Usage: %s \u0026lt;fd\u0026gt;\\n\u0026#34;, argv[0]); return 1; } int fd = atoi(argv[1]); setenv(\u0026#34;ANDROID_USB_FD\u0026#34;, argv[1], 1); execl(\u0026#34;./build/flashrom\u0026#34;, \u0026#34;flashrom\u0026#34;, \u0026#34;-p\u0026#34;, \u0026#34;ch341a_spi\u0026#34;, \u0026#34;--flash-name\u0026#34;, NULL); return 1; } And then:\n1 termux-usb -r -e ./detect_bios /dev/bus/usb/001/003 The output:\nInitializing ch341a_spi programmer Using Android USB FD from parameter: 7 LibUSB initialized with NO_DEVICE_DISCOVERY for FD mode Successfully wrapped USB device from FD 7 Probing for Generic unknown SPI chip (REMS), 0 kB: compare_id: id1 0xa1, id2 0x2818 Found Unknown flash chip \u0026#34;SFDP-capable chip\u0026#34; (16384 kB, SPI). It worked. The CH341A was talking to the BIOS chip. Through my phone. Over USB. Without root.\nThe Recovery With the connection established, I did a full backup first:\n1 termux-usb -r -e ./flash_backup /dev/bus/usb/001/003 The backup took about 2 minutes for the full 16MB chip. Then I verified it — the hash matched between two reads, confirming a stable connection.\nNext, I flashed the correct BIOS — the actual MR9A-H BIOS I had backed up to Google Drive:\n1 2 termux-usb -r -E -e \u0026#39;./build/flashrom -p ch341a_spi \\ -w /storage/emulated/0/Download/backup_chip_full.rom -V\u0026#39; The flash took about 6 minutes. Flashrom reported:\nFound Unknown flash chip \u0026#34;SFDP-capable chip\u0026#34; (16384 kB, SPI). Erasing and writing flash chip... VERIFYING VERIFIED. I disconnected the CH341A clamps from the motherboard. Reconnected power. Pressed power.\nIt booted.\nBIOS screen. Memory detected. CPU running. The motherboard was alive again.\nI verified the flash by reading the chip one more time and comparing the hash against the original file:\nOriginal file: 5fc52519de9b9b9f41e0e62810307d09 Read back from: 5fc52519de9b9b9f41e0e62810307d09 ✅ MATCH What I Learned 1. Budget hardware has limits A $30 X99 board running a 12-core Xeon is not a sustainable combination. The VRM will give up. If you\u0026rsquo;re running enthusiast-class CPUs, invest in a board that can actually deliver the power.\n2. Always triple-check your flash files I had the right backup. I used the wrong file. One filename difference cost me a full day and a CH341A purchase. When flashing firmware, read the filename out loud before pressing Enter.\n3. Android is Linux, but not that Linux SELinux is there for a reason, but it makes hardware access painful without root. The libusb_wrap_sys_device() path is the correct way to handle USB on Android — and it\u0026rsquo;s barely documented.\n4. The open-source ecosystem is incredible Flashrom is maintained by coreboot developers who care deeply about hardware freedom. The fact that I could modify it, compile it on a phone, and recover a motherboard — that\u0026rsquo;s the power of open source in action.\n5. Phones are real computers I did an entire motherboard firmware recovery on a phone. No laptop. No desktop. Just a phone, a $2 CH341A programmer, and some C code. We\u0026rsquo;re living in the future.\nThe Patch The Android FD support patch for flashrom is available in my working copy. If there\u0026rsquo;s interest, I\u0026rsquo;ll clean it up and submit it upstream to the flashrom Gerrit.\nUsage is simple:\n1 2 3 4 5 # With wrapper termux-usb -r -e ./my_wrapper /dev/bus/usb/001/XXX -- -V # Or with environment variable ANDROID_USB_FD=7 ./build/flashrom -p ch341a_spi -c FM25W128 -w firmware.bin My X99 board is alive. My phone earned its place as a recovery tool. And flashrom now has Android support that didn\u0026rsquo;t exist before this weekend.\nSometimes the best code comes from desperation at 11 PM with no computer and a bricked motherboard staring back at you.\nIf you\u0026rsquo;re working on hardware recovery on Android or have questions about the patch, drop a comment below. And if this post helped you, share it — someone out there is probably staring at a bricked board right now with only a phone in their pocket.\nSee you in the Wired\n","permalink":"https://enrell.github.io/en/posts/flashrom-android-fd/","summary":"\u003cp\u003eIt was a Saturday afternoon. I pressed the power button on my PC and\u0026hellip; nothing. Fans spun for a second, LEDs blinked, then dead. Then it tried again. One second, dead. One second, dead. An infinite boot loop with no display output, no beep codes, nothing.\u003c/p\u003e\n\u003cp\u003eWhat followed was a week-long odyssey of diagnostics, wrong guesses, a new motherboard, a bricked BIOS, and — when every computer in my house was dead — an 11 PM coding session on my phone to patch an open-source tool that was never meant to run on Android.\u003c/p\u003e","title":"I Patched Flashrom on My Phone to Recover a Dead Motherboard"},{"content":"It was 7pm on a Wednesday. I was staring at my terminal, watching OpenCode try to answer a question about a library it had never seen before.\nThe LLM was doing its best. But it was hallucinating API endpoints that didn\u0026rsquo;t exist.\nAnd I thought: \u0026ldquo;Why can\u0026rsquo;t my AI just\u0026hellip; search the web?\u0026rdquo;\nThe Problem I use OpenCode, Claude Code and some times Crush as my daily coding companion. It\u0026rsquo;s powerful. But it has a blind spot: The native web fetch can\u0026rsquo;t access claudflare protected sites.\nThat means:\nA lot of fails and wasted tokens. High anti-bot protected site? Inaccessible. Current news from big sources? Unknown. I needed something that lets my LLM search the web and fetch content on demand. Something lightweight. Something I control.\nThe Idea searxng-web-fetch-mcp I wanted an MCP (Model Context Protocol) server that does two things:\nSearch the web — Using my local SearXNG instance Fetch content — Extract clean text from any URL That\u0026rsquo;s it. No bloat. No vendor lock-in. Just search and fetch.\nWhy Crystal? I chose Crystal for three reasons:\nSpeed — Crystal compiles to native code. Fast. Blazing fast. Ergonomics — Ruby-like syntax that\u0026rsquo;s beautiful to read and write. You can build a full app in just a few lines of code. Maintainability — Strong typing catches bugs at compile time. The code base stays clean and easy to maintain. A 12MB binary that starts in milliseconds? That\u0026rsquo;s Crystal\u0026rsquo;s sweet spot.\nFun fact: I started this project on March 25 at 7 PM. By midnight, the core was working. That\u0026rsquo;s how fast Crystal is to develop with.\nThe Stack Component Purpose Crystal Core server, performance Lexbor HTML parsing MCP Protocol AI assistant integration SearXNG Decentralized search Byparr Anti-captcha proxy for fetching Code Stats ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ Language Files Lines Code Comments Blanks ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ Crystal 9 815 689 6 120 Shell 4 414 348 4 62 YAML 1 26 20 0 6 ───────────────────────────────────────────────────────────────────────────────── Markdown 2 241 0 160 81 |- BASH 2 36 16 11 9 |- Crystal 1 13 10 2 1 |- Dockerfile 1 18 17 0 1 |- JSON 1 30 30 0 0 |- YAML 1 82 72 0 10 (Total) 420 145 173 102 ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ Total 16 1675 1202 183 290 ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 689 lines of Crystal. Not bad for a full MCP server, with content extraction and Batch Fetching.\nHow It Works Here\u0026rsquo;s the basic flow:\nLLM → MCP → searxng-web-fetch-mcp ↓ searxng_web_search() ↓ web_fetch() ↓ Clean Markdown → Back to LLM Simple. Elegant. Fast.\nTool 1: searxng_web_search 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 class SearxngWebSearch \u0026lt; MCP::AbstractTool @@tool_name = \u0026#34;searxng_web_search\u0026#34; @@tool_description = \u0026#34;Search the web using SearXNG\u0026#34; def invoke(params) query = params[\u0026#34;query\u0026#34;].as_s num_results = params[\u0026#34;num_results\u0026#34;]?.try(\u0026amp;.as_i) || 10 # Call SearXNG API response = HTTP::Client.get(\u0026#34;#{SEARXNG_URL}/search\u0026#34;, headers: HTTP::Headers{\u0026#34;Accept\u0026#34; =\u0026gt; \u0026#34;application/json\u0026#34;}, query: URI::Params.encode({\u0026#34;q\u0026#34; =\u0026gt; query, \u0026#34;format\u0026#34; =\u0026gt; \u0026#34;json\u0026#34;}) ) parse_results(response.body) end end Tool 2: web_fetch 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 class WebFetch \u0026lt; MCP::AbstractTool @@tool_name = \u0026#34;web_fetch\u0026#34; @@tool_description = \u0026#34;Fetch and extract content from a URL\u0026#34; def invoke(params) url = params[\u0026#34;url\u0026#34;].as_s # Fetch through anti-captcha proxy html = HTTP::Client.get(url) # Extract main content extractor = TrafilaturaExtractor.new result = extractor.extract(html.body) # Convert to clean Markdown markdown = HtmlToMarkdown.convert(result.content) { success: true, text: markdown, metadata: result.metadata } end end The Extraction Algorithm The hardest part was content extraction. Websites are messy. Sidebars, ads, navigation menus — all noise.\nI ported the core logic from go-trafilatura, which uses smart heuristics:\nRemove noise — Scripts, styles, nav, footer, ads Score elements — Based on text density, link density Boost patterns — Class names like \u0026ldquo;content\u0026rdquo;, \u0026ldquo;article\u0026rdquo;, \u0026ldquo;main\u0026rdquo; Penalty patterns — Class names like \u0026ldquo;comment\u0026rdquo;, \u0026ldquo;sidebar\u0026rdquo;, \u0026ldquo;footer\u0026rdquo; Extract metadata — Title, author, date, language from meta tags It works surprisingly well for most articles.\nMulti-Platform Support Because why not? The install script detects your platform automatically:\n1 curl -sL https://raw.githubusercontent.com/enrell/searxng-web-fetch-mcp/main/install.sh | bash Supported platforms:\nLinux: x86_64, arm64, riscv64 macOS: x86_64, arm64 (Apple Silicon) Windows: x86_64 One command. Binary lands in ~/.local/bin. Done.\nConfiguration 1 2 3 4 5 6 7 8 9 10 11 12 { \u0026#34;mcp\u0026#34;: { \u0026#34;searxng-web\u0026#34;: { \u0026#34;type\u0026#34;: \u0026#34;local\u0026#34;, \u0026#34;command\u0026#34;: [\u0026#34;~/.local/bin/searxng-web-fetch-mcp\u0026#34;], \u0026#34;environment\u0026#34;: { \u0026#34;SEARXNG_URL\u0026#34;: \u0026#34;http://localhost:8888\u0026#34;, \u0026#34;BYPARR_URL\u0026#34;: \u0026#34;http://localhost:8191\u0026#34; } } } } Add to your OpenCode config. Restart. The AI can now search and fetch.\nWhat I Learned This project taught me several things:\n1. Environment variables are tricky Crystal\u0026rsquo;s ENV.fetch evaluates at compile time. Passing env vars to child processes? Surprisingly nuanced. I spent hours debugging why npx wasn\u0026rsquo;t receiving my variables.\n2. Linting early saves time Running Ameba (Crystal\u0026rsquo;s linter) on every commit caught 14 issues in one go. Block parameter names, trailing whitespace, formatting — all fixed before they became problems.\n3. Multi-platform releases are a must Users on different OSes and architectures need pre-built binaries. GitHub Actions + Crystal\u0026rsquo;s static linking = magic.\n4. Keep it minimal Two tools. No database. No auth. No complexity. Just search and fetch. That\u0026rsquo;s why it works.\nReal World Example After building this, I asked OpenCode to research a library:\n\u0026gt; Search for the latest \u0026#34;crystal-pg\u0026#34; documentation \u0026gt; Fetch the README from the GitHub repo \u0026gt; Show me how to connect to PostgreSQL And it did. Because it had real information. Not hallucinated guesses.\nWhat\u0026rsquo;s Next 🤖 Better content extraction — Handle more website formats 📊 Response caching — Cache search/fetch results for repeated queries 🔍 Search engine aggregation — Support more search engines 📦 Docker compose — One-click deployment of all services Update: Concurrency Support (v0.2.1) I just released v0.2.1 with fiber-based concurrency! The MCP protocol processes requests sequentially, but I added batch URL fetching that processes multiple URLs in parallel within a single request.\nHow It Works Crystal\u0026rsquo;s spawn creates lightweight fibers. Combined with a semaphore channel, this limits concurrent I/O operations:\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 module Utils module ConcurrentHTTP def self.run_parallel(max_concurrent : Int32, tasks : Array(Proc(T))) : Array(T) forall T semaphore = Channel(Nil).new(max_concurrent) channels = Array(Channel(T | Exception)).new(tasks.size) tasks.each do |task| channel = Channel(T | Exception).new spawn do semaphore.send(nil) begin result = task.call channel.send(result) rescue ex channel.send(ex) ensure semaphore.receive end end end # Collect results... end end end Performance Mode Throughput Sequential search ~2 req/s Batch concurrent (10 URLs) 33 URLs/s Batch concurrent (30 URLs) 25 URLs/s That\u0026rsquo;s ~10-15x faster than sequential processing!\nUsage 1 2 3 4 5 6 7 { \u0026#34;urls\u0026#34;: [ \u0026#34;https://example.com/article1\u0026#34;, \u0026#34;https://example.com/article2\u0026#34;, \u0026#34;https://example.com/article3\u0026#34; ] } Configure the concurrency limit with MAX_CONCURRENT_REQUESTS (default: 30).\nThe key insight: Crystal fibers are lightweight (KB each) vs OS threads (MB each), making them perfect for I/O-bound workloads like HTTP requests.\nTry It 1 2 3 4 5 # Install curl -sL https://raw.githubusercontent.com/enrell/searxng-web-fetch-mcp/main/install.sh | bash # Configure (add to your MCP config) # Then use with OpenCode, Claude Code, or any MCP-compatible client The GitHub repo is open. Issues and PRs welcome.\nWhat about you? Is there a capability your AI assistant is missing? Let me know in the comments.\nSee you in the Wired\n","permalink":"https://enrell.github.io/en/posts/searxng-web-fetch-mcp/","summary":"\u003cp\u003eIt was 7pm on a Wednesday. I was staring at my terminal, watching OpenCode try to answer a question about a library it had never seen before.\u003c/p\u003e\n\u003cp\u003eThe LLM was doing its best. But it was hallucinating API endpoints that didn\u0026rsquo;t exist.\u003c/p\u003e\n\u003cp\u003eAnd I thought: \u003cem\u003e\u0026ldquo;Why can\u0026rsquo;t my AI just\u0026hellip; search the web?\u0026rdquo;\u003c/em\u003e\u003c/p\u003e\n\u003ch2 id=\"the-problem\"\u003eThe Problem\u003c/h2\u003e\n\u003cp\u003eI use OpenCode, Claude Code and some times Crush as my daily coding companion. It\u0026rsquo;s powerful. But it has a blind spot: The native web fetch can\u0026rsquo;t access claudflare protected sites.\u003c/p\u003e","title":"Building a Crystal MCP for Web Search and Content Extraction"},{"content":"\nRecently, I watched a Veritasium video called \u0026ldquo;The Internet Was Weeks Away From Disaster and No One Knew.\u0026rdquo; It dives deep into the history of the XZ Utils backdoor—a highly sophisticated, multi-year social engineering campaign that almost compromised OpenSSH and the entire open-source ecosystem. A malicious actor spent years gaining trust, slowly pushing malicious commits into a deeply buried compression library that everything else depends on.\nWhile I was watching that video, the idea for this project came to my mind. We were inches away from a global catastrophe, and it got me thinking.\nHow many of us actually know what we\u0026rsquo;re running? We run npm install, cargo build, or bun install, and watch the terminal light up with hundreds of packages downloading. We treat the node_modules or target folders like a black box. It’s like plugging your brain directly into the Wired without checking if the connection is compromised. If a core dependency gets hijacked, we are all just sitting ducks.\nI hate flying blind. When I work on a project, I want to see the nervous system. I want to look at the graph of dependencies and see where the weak links are, where the bloat is coming from, and what exactly I’m inviting into my machine.\nSo, I built something to fix that.\nEnter depg (Dependency Graph) I wanted a localized, stupid-fast way to visualize exactly what my project relies on. No uploading my Cargo.lock to some sketchy third-party web service. Just a clean, instantaneous CLI tool that runs locally and maps out the entire dependency tree in an interactive graph.\nI built it in Rust (Edition 2024), naturally, because if we\u0026rsquo;re building CLI tools, we want them to be fast, memory-safe, and effortlessly concurrent.\nSource Code \u0026amp; Installation Source code is available at: https://github.com/enrell/dependencies-graph\nmacOS / Linux:\n1 curl -fsSL https://raw.githubusercontent.com/enrell/dependencies-graph/main/install.sh | sh Windows (PowerShell):\n1 irm https://raw.githubusercontent.com/enrell/dependencies-graph/main/install.ps1 | iex Cargo:\n1 cargo install --git https://github.com/enrell/dependencies-graph.git Usage 1 2 depg run depg run --depth 2 --port 8080 --open How It Works depg searches your current directory for known lockfiles. It parses the complete dependency tree, handling ecosystem-specific resolution semantics. The graph data is serialized and served via an embedded, high-performance Axum web server. The client fetches the graph and renders it instantly onto a physics-driven, responsive canvas. Architecture Backend Core: Rust (Clap, Anyhow, Serde) Extensible Parser Engine: Built using a dynamic traits system making it easy to plug in support for new languages. Web Server: Axum + Tokio (static assets compiled directly into the binary). Frontend Engine: Vanilla JS + Cytoscape.js. You run one command, and boom: your browser opens a fully interactive, physics-based constellation of your project\u0026rsquo;s dependencies. You can zoom in, trace paths, and finally grasp the sheer scale of the shoulders your code is standing on.\nKnowing is Half the Battle Building depg wasn\u0026rsquo;t just about making a cool visualizer; it was about reclaiming visibility. The XZ backdoor proved that open-source software relies heavily on trust, but trust shouldn\u0026rsquo;t mean willful ignorance.\nIf we have the tooling to easily visualize and audit the roots of our software, maybe we can spot the anomalies before they make it into production.\nYou can check out the source code and try it yourself. Run it on your biggest project and tell me—does your dependency graph look like a well-architected city, or a chaotic, tangled mess?\nSee you in the The Wired.\n","permalink":"https://enrell.github.io/en/posts/depg-a-graph-dependencies-visualizer/","summary":"\u003cp\u003e\u003cimg alt=\"graph full\" loading=\"lazy\" src=\"/en/posts/depg-a-graph-dependencies-visualizer/graph-1.png\"\u003e\u003c/p\u003e\n\u003cp\u003e\u003cimg alt=\"graph node selection\" loading=\"lazy\" src=\"/en/posts/depg-a-graph-dependencies-visualizer/graph-2.png\"\u003e\u003c/p\u003e\n\u003cp\u003eRecently, I watched a Veritasium video called \u0026ldquo;The Internet Was Weeks Away From Disaster and No One Knew.\u0026rdquo; It dives deep into the history of the XZ Utils backdoor—a highly sophisticated, multi-year social engineering campaign that almost compromised OpenSSH and the entire open-source ecosystem. A malicious actor spent years gaining trust, slowly pushing malicious commits into a deeply buried compression library that everything else depends on.\u003c/p\u003e","title":"We Are Flying Blind: Why I Built a Dependency Graph Visualizer in Rust"},{"content":"The last post was about architecture decisions.\nThis one is about execution.\nI spent this cycle turning ideas into something runnable and testable. Not polished. Not “AI magic.” Just real foundations.\nWhat shipped since the last post 1) REST API vertical slice navi-agent now has a working API backbone with health, task, and agent routes, including sync flow.\nThis gave me a full path from request → service → persistence → response, which is where real design flaws start to show up.\n2) SQLite persistence for tasks and agents I moved core state out of memory and into SQLite for both tasks and agent metadata.\nThat sounds simple, but it changes everything: restart behavior, debugging, and confidence in iterative testing.\n3) Real agent sync + data-defined agent loading Agent sync is no longer fake.\nAgents are loaded from filesystem definitions and synced into persistence.\nThis aligns with the core principle of navi-agent: agents are data, not hardcoded logic.\n4) REPL/TUI loop for fast local testing I added a simple REPL to exercise behavior quickly without bouncing through HTTP every time.\nCurrent focus is making outputs explicit so it’s obvious what is:\nuser message model “thinking” signal tool response orchestrator final answer That visibility matters more than pretty UI right now.\n5) Sprint 1 orchestrator loop + tool calling I implemented a basic orchestrator loop where the model can request tools through a structured tool-call format, tools execute, and results feed back into the next turn.\nIt supports single and multi-tool call patterns in the same exchange.\n6) Basic MCP integration path A minimal MCP path is wired for tool execution flow, enough to validate the architecture direction without overbuilding too early.\n7) Dev onboarding improved with .env Fresh clone experience now supports .env-based local setup for:\nAPI key default provider default model environment mode (development/production) On startup in development mode, navi-agent prints which .env files were loaded.\nNo guessing, no “why is this config not applying?” confusion.\n8) First-launch config directory creation is explicit navi-agent now creates its user config directory on first launch before command execution.\nThat removes a bunch of hidden friction for first-time contributors.\nTool calling test Logging decision (important) I had a long architecture discussion about telemetry, and I’m choosing the pragmatic path:\nNo OpenTelemetry for now Use Go native log/slog Structured JSON Lines (.jsonl) local logs Rotation Make logs easy for MCP tools to inspect Why: navi-agent is local-first right now. I want observability without dependency bloat or complexity tax.\nThis keeps things minimal, testable, and future-proof.\nProcess note: architect + AI pair execution I rewrote this project multiple times. Prototype → refactor → miss → rewrite again.\nThat’s not chaos anymore. It’s intentional process.\nI’m running an XP-like loop with AI pair programming:\nI own architecture, boundaries, and trade-offs. AI accelerates implementation, test iteration, and mechanical refactors. So rewrites are not failure signals. They are part of design convergence.\nSprint remodel (updated) Sprint 1 — Single-agent runtime stability (current) Simple navi-agent TUI ✅ Basic main orchestrator agent ✅ Basic tool calling ✅ Basic MCP integration ✅ .env onboarding ✅ Agent configuration folder requirements revision/re-think ⏳ Logging foundation (slog + JSONL) ⏳ Goal: TUI asks model to use tools and tool flow is reliable/observable.\nSprint 2 — Specialist vertical slice (no full agency yet) Basic specialist agents: planner, researcher, coder, tester (one active at a time) Basic native MCP tools expansion More CLI commands TUI UX improvements (clarity, traceability, consistency) Goal: TUI can call MCP tools and route to one specialist agent per turn.\nSprint 3 — Agency rollout Controlled multi-agent delegation Guardrails (limits, failure policy, recovery paths) Better orchestration timeline/debug view Goal: multi-agent coordination with predictable behavior and debuggable traces.\nInfrastructure survives hype cycles.\nSo I’m building infrastructure first.\nIf you’re building local-first AI tooling too, I’d love your feedback on the logging direction and sprint structure.\n","permalink":"https://enrell.github.io/en/posts/navi-devlog-1/","summary":"\u003cp\u003eThe last post was about architecture decisions.\u003c/p\u003e\n\u003cp\u003eThis one is about \u003cstrong\u003eexecution\u003c/strong\u003e.\u003c/p\u003e\n\u003cp\u003eI spent this cycle turning ideas into something runnable and testable. Not polished. Not “AI magic.” Just real foundations.\u003c/p\u003e\n\u003ch2 id=\"what-shipped-since-the-last-post\"\u003eWhat shipped since the last post\u003c/h2\u003e\n\u003ch3 id=\"1-rest-api-vertical-slice\"\u003e1) REST API vertical slice\u003c/h3\u003e\n\u003cp\u003enavi-agent now has a working API backbone with health, task, and agent routes, including sync flow.\u003c/p\u003e\n\u003cp\u003eThis gave me a full path from request → service → persistence → response, which is where real design flaws start to show up.\u003c/p\u003e","title":"navi-agent Devlog #1 — Sprint 1 in motion: TUI, orchestrator loop, MCP path, and .env onboarding"},{"content":"It was 3 AM when I had the idea for navi-agent a few months ago. I was in bed thinking about the impact of LLMs on developers\u0026rsquo; hard skills. Before the LLM boom, I improved my coding skills by building projects for my own use. But when OpenAI launched GPT-3, I saw that this technology could be useful. I spent a lot of time playing with GPT-3 code generation, and I remember the feeling I had when I used it to learn OOP. I was like, \u0026ldquo;What the F*! How the f* do these guys do that?\u0026rdquo; That was the spark that made my hyperfocus kick in to study the area.\nI studied the basics to understand all model architectures, and recently I completed the Information Retrieval and Artificial Intelligence course in college. Now I have solid knowledge to start understanding the present and future of LLMs.\nIn this article, I will present the architectural decisions behind navi-agent, and some useful insights about agent strengths and weaknesses based on my humble LLM knowledge.\nThe difference between agents and agency Look, I\u0026rsquo;ve been deep in the AI space for a while now, and I can tell you one thing: everyone throws around the word \u0026ldquo;agent\u0026rdquo; like it\u0026rsquo;s going out of style. But here\u0026rsquo;s the thing — an agent by itself? It\u0026rsquo;s just a fancy function call with commitment issues.\nLet me break it down with something real. Imagine you have a single AI model that can answer questions. Cool, right? That\u0026rsquo;s an agent. It\u0026rsquo;s reactive. You ask, it answers. You prompt, it responds. Nothing wrong with that — but it\u0026rsquo;s not exactly\u0026hellip; autonomous.\nNow, what if that same AI could decide when to search the web, choose to call a database, and opt to save results somewhere? Still an agent. But the moment you connect multiple agents together, give them roles, responsibilities, and a way to communicate?\nThat\u0026rsquo;s when you have an agency.\nThe Solo Agent Trap I\u0026rsquo;ve seen this mistake too many times. Developers build a \u0026ldquo;super agent\u0026rdquo; that tries to do everything:\n1 2 3 4 5 6 7 8 // Don\u0026#39;t do this func (a *Agent) HandleEverything(input string) string { // Check if it needs to search // Check if it needs to calculate // Check if it needs to save // Check if it needs to call API // ...you get the idea } This is what I call the \u0026ldquo;god agent\u0026rdquo; pattern. It works for demos, but falls apart in production. Why? Because single agents lack perspective. They\u0026rsquo;re trying to be everything at once.\nThe Agency Approach An agency is different. Think of it like a team:\n1 2 3 4 5 6 7 // This is the way type Agency struct { Planner *Agent // Decides the steps Researcher *Agent // Searches and gathers info Coder *Agent // Writes and reviews code Executor *Agent // Runs tools and APIs } Each agent has a single responsibility. The planner doesn\u0026rsquo;t code. The coder doesn\u0026rsquo;t execute. The executor doesn\u0026rsquo;t plan. They specialize, communicate, and together they solve problems that would overwhelm any single agent.\nWhy This Matters for navi-agent When I started designing navi-agent, I made the agent mistake first. I built a monolithic agent that tried to handle orchestration, tool execution, memory management, and response formatting all at once.\nIt was a mess.\nThe breakthrough came when I realized: navi-agent isn\u0026rsquo;t an agent. navi-agent is an agency. It\u0026rsquo;s a system where specialized agents work together, each with clear boundaries and purposes.\nAspect Agent Agency Scope Single task Coordinated workflow Decision Making Reactive Strategic + Reactive Failure Mode All or nothing Graceful degradation Scalability Limited Horizontal Maintenance Hard to debug Clear responsibility The moment you understand this difference, your entire approach to AI orchestration changes. You stop asking \u0026ldquo;How do I make my agent smarter?\u0026rdquo; and start asking \u0026ldquo;How do I make my agents work better together?\u0026rdquo;\nThat\u0026rsquo;s the foundation navi-agent was built on.\nLLM Weaknesses I love this tech. I studied it. I built with it. I\u0026rsquo;m building navi-agent on top of it. But here\u0026rsquo;s the thing I learned the hard way: if you don\u0026rsquo;t understand where LLMs break, you\u0026rsquo;re not building infrastructure — you\u0026rsquo;re building a house of cards.\nLet me share what I discovered after countless debugging sessions at 4 AM.\nThe Context Wall That Nobody Talks About Everyone celebrates bigger context windows like we solved everything. Cool, but here\u0026rsquo;s what actually happens: your model starts forgetting stuff before it even hits the limit.\nI was building this feature where navi-agent needed to remember conversation history plus tool results plus system instructions. Sounds simple, right? Well, around token 8,000 on a 32K model, things got weird. The model would:\nIgnore instructions I placed at the beginning Start giving generic answers Hallucinate more frequently Lose track of constraints It\u0026rsquo;s like when you study for 8 hours straight and by hour 7 you\u0026rsquo;re just\u0026hellip; reading words without absorbing anything.\n1 2 3 4 5 6 7 // What I thought would work func BuildContext(history, tools, instructions string) string { return instructions + history + tools // Simple concatenation, right? } // What actually happens inside the model // Attention dilution = 📉 The lesson? More context ≠ more intelligence. Sometimes more context = more confusion.\nThe Confidence Problem LLMs have no idea when they\u0026rsquo;re wrong. None. Zero. They\u0026rsquo;ll tell you the most confidently incorrect answer with 99% certainty.\nI built a test where I asked the same question 100 times with slight variations. The model would give contradictory answers, each time sounding absolutely certain. That\u0026rsquo;s when it hit me: confidence ≠ correctness.\n1 2 3 4 5 type LLMResponse struct { Answer string Confidence float64 // This number means nothing IsCorrect bool // The model doesn\u0026#39;t know this } If you\u0026rsquo;re building systems that execute code, handle money, or touch security — and you\u0026rsquo;re trusting self-reported confidence — buddy, you\u0026rsquo;re gonna have a bad time.\nThe Memory That Isn\u0026rsquo;t There LLMs don\u0026rsquo;t have memory. They don\u0026rsquo;t learn from conversations. They don\u0026rsquo;t update their knowledge. After every response, it\u0026rsquo;s like they\u0026rsquo;re born again — pure amnesia with swagger.\nEverything you think is \u0026ldquo;memory\u0026rdquo; is actually built by the developer. Without external memory architecture, you have a goldfish with a PhD. Brilliant, but forgets everything in 3 seconds.\nThe Generalist Trap LLMs know everything and nothing at the same time. Ask about Kubernetes, quantum physics, anime plot lines, and medieval history in the same conversation? No problem. But dig deeper on any single topic, and you\u0026rsquo;ll find the limits.\nThey\u0026rsquo;re statistical generalists. Pattern matchers on steroids. Which is incredible for breadth, but dangerous when you need depth.\nI learned this when I was vibe coding with active reviewing using Claude Opus models. It was generating code that looked perfect but had subtle bugs. The model knew the syntax, understood the pattern, but missed the edge cases because it doesn\u0026rsquo;t actually understand code — it predicts tokens based on patterns it\u0026rsquo;s seen.\nThe Hallucination Reality Let\u0026rsquo;s be clear: hallucination isn\u0026rsquo;t a bug, it\u0026rsquo;s a feature. Well, not really a feature, but an inevitable byproduct of how these models work. They predict tokens probabilistically. Sometimes that prediction is wrong. And they have zero idea when it happens.\nWhat makes it worse:\nAmbiguous instructions Conflicting context Too much information Questions about things that don\u0026rsquo;t exist 1 2 3 4 // The scary part response := model.Ask(\u0026#34;Something that doesn\u0026#39;t exist\u0026#34;) fmt.Println(response) // Returns something plausible fmt.Println(model.KnowsItsWrong()) // false - method doesn\u0026#39;t exist Why Multi-Agent Systems Actually Matter So why all this talk about weaknesses? Because understanding them changes everything about how you build.\nMost people think multi-agent systems are about speed. Parallelization. Getting things done faster. They\u0026rsquo;re wrong.\nIt\u0026rsquo;s about cognitive distribution.\nWhen I split navi-agent into specialized agents, something interesting happened:\nSingle Agent Agency Context dilution Focused context per agent High hallucination rate Lower per-agent hallucination Hard to debug Clear failure boundaries Generic responses Specialized outputs Everything fails together Graceful degradation Each agent operates in a smaller cognitive scope. Smaller scope means less confusion, better instruction adherence, and easier debugging.\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 // Before: One agent trying to do everything type GodAgent struct { // Planning // Research // Coding // Execution // Verification // Memory // Everything... } // After: Specialized agents type Agency struct { Planner *Agent // Just plans Researcher *Agent // Just researches Coder *Agent // Just codes Executor *Agent // Just executes Verifier *Agent // Just verifies } An agency isn\u0026rsquo;t necessarily faster by default. It\u0026rsquo;s more stable. Speed is a side effect. Stability is the goal.\nThe Hexagonal Architecture Decision The AI landscape is in constant flux. New API versions, model capabilities, providers, and technologies emerge every month.\nSo how does this connect to architecture? Simple: if LLMs are inherently volatile and the AI field evolves so rapidly, your architecture needs to be decoupled to protect your system from these shifts.\nHexagonal architecture (ports and adapters) gives me:\nClear boundaries between LLM logic and business logic Testable interfaces without calling actual models Swappable implementations (swap implementations without changing core) Isolation of hallucination-prone components 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 // Port: What the LLM can do type LLMPort interface { Generate(ctx context.Context, prompt Prompt) (Response, error) Embed(ctx context.Context, text string) (Vector, error) } // Adapter: How we actually do it (OpenAI, Anthropic, local, etc.) type OpenAIAdapter struct { client *openai.Client } // Core: Business logic that doesn\u0026#39;t care which LLM type Orchestrator struct { llm LLMPort // Doesn\u0026#39;t know or care about the implementation } This isn\u0026rsquo;t just clean code — it\u0026rsquo;s survival. Let me be real about what can change in the next 12 months:\nTechnology changes — OpenAI changes their API. Anthropic launches a new protocol. Your architecture determines if this is a Tuesday afternoon config update or days of rewrites. Fallback scenarios — OpenAI goes down. Rate limits hit. Your API key gets throttled. Hexagonal architecture means your orchestrator doesn\u0026rsquo;t care — it just calls the port, and the adapter handles the failover. Hybrid deployments — Some agents run locally for privacy, others in the cloud for power. Some use OpenAI, others use Claude, others use open-source models you host yourself. Same interface, different adapters. 1 2 3 4 5 6 7 8 9 // The orchestrator doesn\u0026#39;t know or care type Orchestrator struct { llm LLMPort // Could be OpenAI // Could be Anthropic // Could be your local Ollama instance // Could be a load balancer across 5 providers // Doesn\u0026#39;t matter. Interface stays the same. } This is why hexagonal architecture isn\u0026rsquo;t overengineering — it\u0026rsquo;s acknowledging that the LLM landscape will change. The question isn\u0026rsquo;t if you\u0026rsquo;ll need to adapt. It\u0026rsquo;s whether your architecture lets you adapt in hours or months.\nWhat I Learned LLMs are tools, not thinkers — They\u0026rsquo;re incredibly powerful pattern matchers, but they don\u0026rsquo;t \u0026ldquo;understand\u0026rdquo; like we do. Architecture matters more than ever — Bad architecture with LLMs doesn\u0026rsquo;t just break, it breaks unpredictably. Specialization beats generalization — Both for agents and for the systems that contain them. Human oversight is non-negotiable — No matter how autonomous your system is, keep humans in the loop for critical decisions. The hype cycle is real — Ignore the \u0026ldquo;AI will replace developers\u0026rdquo; noise. Build useful things. Solve real problems. What\u0026rsquo;s Next for navi-agent I\u0026rsquo;m still early in this journey. The architecture is stabilizing, but there\u0026rsquo;s still so much code to write and decisions to make, like:\nSandbox Memory management with vector stores Security layers against prompt injection Observability for agent interactions Self-healing workflows Human-in-the-loop interfaces But the foundation is solid. And it\u0026rsquo;s solid because I designed it around LLM weaknesses, not their strengths.\nIf you\u0026rsquo;re building with LLMs, I\u0026rsquo;d love to hear your war stories. What architectural mistakes did you make? What worked? What completely failed?\nHit me up in the comments, on X or Discord. And if you\u0026rsquo;re curious about navi-agent\u0026rsquo;s progress, the GitHub repo is where all the messy experimentation happens in public (there\u0026rsquo;s no code yet, but soon).\nRemember: infrastructure survives bubbles. Hype doesn\u0026rsquo;t. Build the former.\n","permalink":"https://enrell.github.io/en/posts/navi-arquitecture/","summary":"\u003cp\u003eIt was 3 AM when I had the idea for navi-agent a few months ago. I was in bed thinking about the impact of LLMs on developers\u0026rsquo; hard skills. Before the LLM boom, I improved my coding skills by building projects for my own use. But when OpenAI launched GPT-3, I saw that this technology could be useful. I spent a lot of time playing with GPT-3 code generation, and I remember the feeling I had when I used it to learn OOP. I was like, \u0026ldquo;What the F*! How the f* do these guys do that?\u0026rdquo; That was the spark that made my hyperfocus kick in to study the area.\u003c/p\u003e","title":"Defining the architecture decisions of navi-agent"},{"content":"Hello world guys! The TL;DR is: I\u0026rsquo;ve tested openclaw and other AI orchestrators, and they always follow the exact same pattern:\nThey are built as products to be sold, not as open-source projects for the community. They are created by the hype and for the hype, pushing a generic idea of \u0026ldquo;agency\u0026rdquo;—a bloated product with a bunch of features and skills that, at the end of the day, aren\u0026rsquo;t even that useful. That\u0026rsquo;s because they aren\u0026rsquo;t built to solve real problems; they\u0026rsquo;re built for marketing and to sell big tech subscriptions. That\u0026rsquo;s exactly why OpenAI hired Peter Steinberger—a classic acqui-hire just to have another avenue to sell their API keys and subscriptions, not to solve actual problems.\nI can name a few projects that follow this exact pattern:\nWindsurf Adept Covariant All hyped up as the \u0026ldquo;next generation of agents/coding/robotics\u0026rdquo;. Google, Amazon, and Meta swoop in with licensing deals and poach the founders/team.\nThe result? The startups turn into ghost companies or run on skeleton crews. Employees cry in all-hands meetings, and the product dies or fades into irrelevance while the team goes off to work on big tech clouds/models.\nnavi-agent\u0026rsquo;s differentiator navi-agent\u0026rsquo;s purpose is to be a useful, secure agent orchestrator built for the tech/dev community.\nI\u0026rsquo;ve pointed out some useless aspects of openclaw, and you might be wondering why I\u0026rsquo;m thrashing openclaw specifically instead of other AI orchestrators. The answer is simple: because openclaw is the most popular, the most hyped, the most sold, and the most used right now. It’s the perfect example. Anyone in the tech bubble who hasn\u0026rsquo;t heard of openclaw in the last few weeks has been living under a rock.\nI won\u0026rsquo;t deny that I\u0026rsquo;m in a bubble, that LLMs form a bubble that could pop at any minute, and that an AI winter will arrive sooner or later. But my point is: even if it is a bubble, even with all the hype and massive big tech investments, some companies will survive and open-source models will stick around. That\u0026rsquo;s because there is real value and real demand; it\u0026rsquo;s just not the kind of value and demand big tech is trying to sell. It\u0026rsquo;s a more niche, specific, real, and useful demand. And that’s navi-agent\u0026rsquo;s goal: to survive the bubble bursting.\nI believe the bubble will pop, and the companies that survive will be the ones actually delivering value, whether that\u0026rsquo;s a product, a service, or foundation models. LLMs are highly useful technologies, especially in these areas (in no particular order):\nThe utility of agents Software Development: It has never been easier to get a project off the ground. Testing ideas, learning a new framework, or just doing some \u0026ldquo;vibe coding\u0026rdquo; to see if a product holds up has become absurdly fast. The trap here is for those who just copy and paste without understanding what\u0026rsquo;s going on under the hood. (If you want to avoid that and learn something useful, check out this post: how to use LLMs the right way).\nHeadache-free Customer Support: Forget those dumb bots from the past. A smart agent integrated into WhatsApp or internal systems handles 60% to 80% of the daily grind, 24/7. I have friends who have worked in support, and the reality is: 90% of issues are mundane things, often from the elderly or people with zero digital literacy. AI handles this without breaking a sweat.\nSales and Marketing at scale (without sounding like a robot): You can automate everything from lead qualification (SDR agents) to abandoned cart recovery and call analysis. It’s about generating content and sending personalized mass emails that actually sound natural.\nFinding things within the company (the famous RAG): You know that massive legacy documentation or internal policies nobody knows where to find? Point an internal chat at it. If implemented well, documenting and querying company knowledge becomes trivial.\nCybersecurity and Blue/White Hats: Parsing logs by hand is boring and repetitive. A well-tuned model can chew through logs, detect threats, run superficial pentests, and generate reports in no time. It\u0026rsquo;s an absolute lifesaver for security folks who need to reduce incident response times.\nDespite all the benefits agents bring, most people have an exaggerated view of them and, more often than not, give them superpowers they shouldn\u0026rsquo;t have. The catch is thinking that agents can solve everything. That couldn\u0026rsquo;t be further from the truth: they should be part of the process, not take over the entire process.\nSecurity One of the saddest things in the tech bubble right now is the sheer negligence regarding security. It doesn\u0026rsquo;t need to be enterprise-grade, but it has to be solid; have a minimum level of responsibility. Don\u0026rsquo;t be like certain devs: don\u0026rsquo;t deploy a hobby project with open ports, plaintext credentials, and 1-click Remote Code Execution. Even explicitly warning people multiple times isn\u0026rsquo;t enough—it\u0026rsquo;s a recipe for disaster.\nThe navi-agent project After that extensive rant, it\u0026rsquo;s time to talk about the project that\u0026rsquo;s been pulsing in my mind: navi-agent. The idea came to me years ago as a personal project (I\u0026rsquo;m clearly late to the party, right? XD). I\u0026rsquo;ve always looked for ways to automate my dev environment, whether it was self-hosted apps, CLI tools, or automation scripts, but I was never satisfied with how the automation was done, and I figured out why.\nTask automation, especially on Linux, is extremely decentralized. This means we have plenty of automation tools, but they don\u0026rsquo;t communicate natively or in a standardized way. You have a bash script for one thing, a cronjob for another, and several excellent CLI tools that require you to write ugly \u0026ldquo;glue code\u0026rdquo; just to make them talk to each other. The tools don\u0026rsquo;t talk to each other, and they shouldn\u0026rsquo;t, for security reasons.\nnavi-agent was born exactly to be that link, the maestro of this orchestra, but with one golden rule: you are always in control, and security is non-negotiable. It\u0026rsquo;s not a \u0026ldquo;magical autonomous agent\u0026rdquo; that will run rm -rf / because it hallucinated mid-task or misinterpreted a loose prompt.\nThe name comes from NAVI, a computer from the anime Serial Experiments Lain (1998). It\u0026rsquo;s one of my favorite anime and I highly recommend it: it\u0026rsquo;s dense, intellectual, and philosophical. Anyway, this computer is used by the protagonist, Lain, to access the Wired (the ultra-advanced global network in the anime, sort of like an internet that mixes virtual reality, collective consciousness, and much more). NAVI is the hardware + software bundle to access the Wired. It features both navigation-based and voice-based interfaces.\nNAVI: She does an insane upgrade to her NAVI: navi-agent\u0026rsquo;s Architecture To ensure the project is robust, scalable, and above all, testable, I chose to write navi-agent in Go. Besides delivering absurd performance and compiling everything into a single binary (which makes life on Linux so much easier), Go allows me to handle concurrency in a very elegant and straightforward way.\nThe foundation of the project follows the Hexagonal Architecture (Ports and Adapters).\nThis means navi-agent\u0026rsquo;s core intelligence and orchestration are completely isolated from external tools and interfaces. If tomorrow I want to swap out the LLM provider, the local database, or how it executes a script on my OS, I just write a new \u0026ldquo;adapter\u0026rdquo;. The business logic remains intact and isolated from side effects.\nHow do you interact with it? As I mentioned at the beginning, navi-agent doesn\u0026rsquo;t lock you into a heavy, proprietary web UI that tries to shove a subscription down your throat, nor is it an agent that will expose your credentials. It was designed to have multiple entry points (the Ports in our architecture):\nTUI (Terminal User Interface): For terminal dwellers (and anyone who uses Neovim and a window manager like Sway knows the value of never having to take your hands off the keyboard), having a fast, beautiful, and responsive interface right in the console is essential.\nREST/gRPC API: If you want to integrate navi-agent into another system, build your own frontend, or trigger webhooks from other applications, the door is open.\nMessaging Bots: Native integration with Discord and Telegram. You can trigger automations on your server or your home machine by sending a text from your phone, in a secure and authenticated way.\nThe roadmap is still being drawn up, and the core is currently under development. The goal is to build something open-source, focusing on solving real productivity and system orchestration problems, without selling our souls to the hype.\nI\u0026rsquo;ll be dropping the GitHub repository soon for anyone who wants to take a look at the code (or chip in on the PRs). Until then, we keep coding!\n\u0026ndash; Present day, present time! hahahahaha\n","permalink":"https://enrell.github.io/en/posts/navi-the-ai-orchestrator/","summary":"\u003ch1 id=\"hello-world-guys\"\u003eHello world guys!\u003c/h1\u003e\n\u003cp\u003eThe TL;DR is: I\u0026rsquo;ve tested openclaw and other AI orchestrators, and they always follow the exact same pattern:\u003c/p\u003e\n\u003cblockquote\u003e\n\u003cp\u003eThey are built as products to be sold, not as open-source projects for the community. They are created by the hype and for the hype, pushing a generic idea of \u0026ldquo;agency\u0026rdquo;—a bloated product with a bunch of features and skills that, at the end of the day, aren\u0026rsquo;t even that useful. That\u0026rsquo;s because they aren\u0026rsquo;t built to solve real problems; they\u0026rsquo;re built for marketing and to sell big tech subscriptions.\nThat\u0026rsquo;s exactly why OpenAI hired Peter Steinberger—a classic acqui-hire just to have another avenue to sell their API keys and subscriptions, not to solve actual problems.\u003c/p\u003e","title":"I'm building navi-agent: a truly secure and useful AI orchestrator | cry about it openclaw"},{"content":"It was 3 AM on a Tuesday night. I was staring at my anime folder, scrolling through filenames like:\n[SubsPlease] Spy x Family - 01 (1080p) [A4DAF3D9].mkv [Coalgirls] Clannad (1920x1080 Blu-Ray FLAC) [1234ABCD]/[Coalgirls] Clannad - 01 (1920x1080 Blu-Ray FLAC) [1234ABCD].mkv One Punch Man S02E03 1080p WEBRip x264-PandoR.mkv And I thought to myself: \u0026ldquo;There has to be a better way.\u0026rdquo;\nSound familiar? If you\u0026rsquo;ve ever built a media library, you know exactly what I\u0026rsquo;m talking about. Those messy, inconsistent filenames — they drive me crazy. And the existing parsers? Either too slow, too rigid, or didn\u0026rsquo;t handle the wild variety of naming conventions we anime fans use.\nSo I built one myself.\nMeet Zantetsu Zantetsu (Japanese for \u0026ldquo;cutting blade\u0026rdquo; — sharp, fast, precise) is my solution to this problem. It\u0026rsquo;s a blazing-fast anime metadata parser that extracts title, episode number, resolution, codecs, and more from any filename you throw at it.\nnpm install zantetsu One line. That\u0026rsquo;s all it takes to start parsing.\nWhy This Matters Here\u0026rsquo;s what Zantetsu can do:\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 import { parse } from \u0026#39;zantetsu\u0026#39;; const result = parse(\u0026#39;[SubsPlease] Spy x Family - 01 (1080p).mkv\u0026#39;); // Result: // { // title: \u0026#34;Spy x Family\u0026#34;, // episode: { type: \u0026#39;single\u0026#39;, episode: 1 }, // resolution: \u0026#39;FHD1080\u0026#39;, // group: \u0026#39;SubsPlease\u0026#39;, // video_codec: \u0026#39;H264\u0026#39;, // audio_codec: \u0026#39;AAC\u0026#39;, // source: \u0026#39;WEB\u0026#39;, // confidence: 0.85 // } But it gets better. Zantetsu handles the chaos:\nMulti-episode files? ✅ Season notations (S01E03)? ✅ Resolution variants (1080p, 1080i, 1080)? ✅ Release groups and codecs? ✅ Batch processing for entire folders? ✅ The Story Behind It I didn\u0026rsquo;t just wake up and decide to write a parser. This project started because I needed it for my own media server. I was tired of manually renaming files or using clunky tools that couldn\u0026rsquo;t keep up with the anime community\u0026rsquo;s creative naming conventions.\nSo I did what any developer does: I solved my own problem.\nBut I didn\u0026rsquo;t want just another regex-based parser. I wanted something fast. Something that could process thousands of filenames in seconds. That\u0026rsquo;s why I chose Rust for the core engine — it gives me native performance with the safety guarantees I need.\nThe TypeScript bindings? That\u0026rsquo;s for developer experience. Because parsing should be enjoyable, not a debugging nightmare.\nThe Stack Here\u0026rsquo;s what makes Zantetsu tick:\nLayer Technology Why Core Parser Rust Raw speed, memory safety Bindings TypeScript Developer experience Build Cargo + npm Best of both worlds The result? A package that\u0026rsquo;s 10x faster than pure JavaScript alternatives — but still feels like native JavaScript to use.\nReal World Example Let\u0026rsquo;s say you have a folder with mixed content:\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 import { parseBatch } from \u0026#39;zantetsu\u0026#39;; const files = [ \u0026#39;[SubsPlease] Spy x Family - 01 (1080p).mkv\u0026#39;, \u0026#39;[Coalgirls] Clannad - 02 (720p) [ABC123].mkv\u0026#39;, \u0026#39;One Punch Man S02E03 1080p WEBRip.mkv\u0026#39;, \u0026#39;[Erai-raws] Made in Abyss S1 - 04 [1080p][Multiple Subtitle].mkv\u0026#39; ]; const results = parseBatch(files); // Process them however you want results.forEach(r =\u0026gt; { console.log(`${r.title} - Episode ${r.episode?.episode}`); }); Output:\nSpy x Family - Episode 1 Clannad - Episode 2 One Punch Man - Episode 3 Made in Abyss - Episode 4 Beautiful, right?\nWhat I Learned This was my first time publishing an NPM package, and wow — there\u0026rsquo;s a lot more to it than I expected:\nVersioning matters — Semantic versioning isn\u0026rsquo;t optional Type definitions are essential — Your users will thank you Documentation is a feature — I spent as much time on docs as code Testing is non-negotiable — 80% test coverage minimum Community feedback is gold — Early users find bugs you never imagined What\u0026rsquo;s Coming Next I\u0026rsquo;m just getting started. Here\u0026rsquo;s what\u0026rsquo;s on the roadmap:\n🤖 ML-powered parsing — For the really weird filenames 📺 Multi-episode detection — Handling batch releases 🎨 Custom rules API — Add your own parsing patterns 🌐 More media types — Support for movies, TV shows, music Try It Out I\u0026rsquo;ve open-sourced Zantetsu because I believe in giving back to the community that inspired it. Whether you\u0026rsquo;re building a media server, a torrent indexer, or just need to organize your anime folder — this tool is for you.\n1 npm install zantetsu And if you run into issues, find a bug, or have a feature request — the GitHub repo is always open. I\u0026rsquo;d love to hear your feedback.\nWhat about you? Is there a problem in your daily workflow that you\u0026rsquo;ve been putting off solving? Let me know in the comments — maybe together we can build something awesome.\nAlso, if you found this useful, share it with fellow developers. It helps more than you know.\n","permalink":"https://enrell.github.io/en/posts/zantetsu-intro/","summary":"\u003cp\u003eIt was 3 AM on a Tuesday night. I was staring at my anime folder, scrolling through filenames like:\u003c/p\u003e\n\u003cpre tabindex=\"0\"\u003e\u003ccode\u003e[SubsPlease] Spy x Family - 01 (1080p) [A4DAF3D9].mkv\n[Coalgirls] Clannad (1920x1080 Blu-Ray FLAC) [1234ABCD]/[Coalgirls] Clannad - 01 (1920x1080 Blu-Ray FLAC) [1234ABCD].mkv\nOne Punch Man S02E03 1080p WEBRip x264-PandoR.mkv\n\u003c/code\u003e\u003c/pre\u003e\u003cp\u003eAnd I thought to myself: \u003cem\u003e\u0026ldquo;There has to be a better way.\u0026rdquo;\u003c/em\u003e\u003c/p\u003e\n\u003cp\u003eSound familiar? If you\u0026rsquo;ve ever built a media library, you know exactly what I\u0026rsquo;m talking about. Those messy, inconsistent filenames — they drive me crazy. And the existing parsers? Either too slow, too rigid, or didn\u0026rsquo;t handle the wild variety of naming conventions we anime fans use.\u003c/p\u003e","title":"I Built a NPM Package for Parsing Anime Filenames — Here's My Story"},{"content":"Hello, world! First of all, I want to say that this is my first post in a long time, and I hope to keep writing more often. I have been working on a MCP (Model Context Protocol) for a while now, and I have been learning a lot about typescript AST manipulation. I want to share some of my learnings with you.\nWhat is a MCP (Model Context Protocol)? MCP is an open protocol that allows you to standardize the way that applications provide context to their models.\nWhat this means is that you can use the MCP to provide context (data) to LLMs, and the LLMs will be able to use this context to generate better responses.\nLet\u0026rsquo;s say that you ask the LLM \u0026ldquo;What is the weather?\u0026rdquo; and the LLM doesn\u0026rsquo;t know the answer, because they can access real-time data. If you provide to LLM the current weather in Brazil, it will be able to generate a correct response right?\nWell, that\u0026rsquo;s what the MCP is all about. It allows you to provide context to the LLMs so that they can generate better responses.\nYou can use a variety of data sources to provide context to the LLMs, including databases, APIs, and even user input. The key is to make sure that the context is relevant and up-to-date so that the LLMs can generate the best possible responses.\nFor this example you can use a simple MCP server that receives a City name for example \u0026ldquo;Sao Paulo\u0026rdquo; and returns the current weather in that city.\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 from mcp.server.fastmcp.server import FastMCP import requests mcp = FastMCP() @mcp.tool() def get_weather(city: str, state: str, country: str) -\u0026gt; dict: \u0026#34;\u0026#34;\u0026#34; Get the current weather for a given city, state, and country using OpenWeatherMap API. \u0026#34;\u0026#34;\u0026#34; API_KEY = \u0026#34;YOUR_API_KEY\u0026#34; # Step 1: Get latitude and longitude geo_url = f\u0026#34;http://api.openweathermap.org/geo/1.0/direct?q={city},{state},{country}\u0026amp;limit=1\u0026amp;appid={API_KEY}\u0026#34; geo_resp = requests.get(geo_url) geo_data = geo_resp.json() if not geo_data: return {\u0026#34;error\u0026#34;: \u0026#34;Location not found\u0026#34;} lat = geo_data[0][\u0026#34;lat\u0026#34;] lon = geo_data[0][\u0026#34;lon\u0026#34;] # Step 2: Get weather data weather_url = f\u0026#34;https://api.openweathermap.org/data/3.0/onecall?lat={lat}\u0026amp;lon={lon}\u0026amp;appid={API_KEY}\u0026amp;units=metric\u0026#34; weather_resp = requests.get(weather_url) weather_data = weather_resp.json() if \u0026#34;current\u0026#34; not in weather_data: return {\u0026#34;error\u0026#34;: \u0026#34;Weather data not found\u0026#34;} return { \u0026#34;city\u0026#34;: city, \u0026#34;temperature\u0026#34;: weather_data[\u0026#34;current\u0026#34;][\u0026#34;temp\u0026#34;], \u0026#34;description\u0026#34;: weather_data[\u0026#34;current\u0026#34;][\u0026#34;weather\u0026#34;][0][\u0026#34;description\u0026#34;].capitalize() } With this MCP server, you can provide context to the LLMs by calling the get_weather method with a city and country name. The server will then return the current weather in that city.\nExample of a response 1 2 3 4 5 { \u0026#34;city\u0026#34;: \u0026#34;Sao Paulo\u0026#34;, \u0026#34;temperature\u0026#34;: 25.5, \u0026#34;description\u0026#34;: \u0026#34;Sunny\u0026#34; } With this response, the LLM will be able to generate a better response to the question \u0026ldquo;What is the weather in Sao Paulo?\u0026rdquo; by using the context provided by the MCP server, that gets the current weather information from OpenWeatherMap API.\nAST (Abstract Syntax Tree) AST (Abstract Syntax Tree) is a representation of the structure of source code. It is a tree-like data structure that represents the syntax of the code in a way that is easy to analyze and manipulate.\nAST is used in many programming languages to represent the structure of the code, and it is a powerful tool for analyzing and manipulating code.\nTypescript AST viewer site\nWhy i\u0026rsquo;m study MCP and AST? Because I\u0026rsquo;m working on ast-mcp, a MCP for AST operations, providing a server for LLMs to interact with ASTs.\nThe project is still in its early stages, but I hope to make it a powerful tool for developers to analyze and manipulate code using a more efficient approach.\nI hope you found this post useful, and I look forward to sharing more learnings with you in the future. If you have any questions or suggestions, feel free to reach out to me on X or Discord.\nIf you want to contribute to the project, feel free to open an issue on the GitHub repository.\n","permalink":"https://enrell.github.io/en/posts/mcp-ast-learnings/","summary":"\u003ch1 id=\"hello-world\"\u003eHello, world!\u003c/h1\u003e\n\u003cp\u003eFirst of all, I want to say that this is my first post in a long time, and I hope to keep writing more often. I have been working on a MCP (Model Context Protocol) for a while now, and I have been learning a lot about typescript AST manipulation. I want to share some of my learnings with you.\u003c/p\u003e\n\u003ch1 id=\"what-is-a-mcp-model-context-protocol\"\u003eWhat is a MCP (Model Context Protocol)?\u003c/h1\u003e\n\u003cp\u003eMCP is an open protocol that allows you to standardize the way that applications provide context to their models.\u003c/p\u003e","title":"Sharing some learnings about MCP and AST"}]