Author: Mark

  • Open WebUI made me build my own LLM harness

    Open WebUI is a web app for chatting with LLMs, like chatgpt.com or claude.ai. I’ve run it for the last 6 months, and I’m done with it. I’m building my own replacement.

    Why I chose it

    I wanted an open-source and self-hostable UI, so I don’t support the big AI companies. I chose Open WebUI because it seemed like the most feature-rich and least resource-hungry web UI for LLMs. The alternatives didn’t fit. LibreChat requires a stack of 6 containers. I wanted a single container. Most other alternatives are native desktop apps. I wanted something that syncs between my phone and my laptop.

    It’s a resource hog

    Open WebUI takes nearly 1GB of RAM on my 2GB VPS. Its backend is Python, and its frontend is a single-page app, and both are heavy.

    It also bundles Whisper (speech-to-text) and embedding models by default, which makes even the slim container image nearly 1.5GB.

    The bugs are awful

    Over the last few months, I’ve hit some insane bugs.

    The defaults and UX are awful

    Without the bugs, I still hate the defaults. The configuration is unmanageable. Each model has to be configured independently.

    When you start a chat, it tries to connect to every MCP server. Why? Some are slow, and I have a lot of them.

    You can only have one Web Search provider at a time. I bounce between Kagi and Exa because they behave differently, so I added them as MCPs instead, which lets me use both.

    I have to configure each model I use or switch to. Every time I try a new model, I configure it to have web search, each of my MCPs, Code Execution, and Native Function Calling. These are all disabled by default, and can only be enabled on one model at a time. It’s such an unsustainable way to manage your configuration!

    The buttons don’t make sense.

    CMD + R means “refresh the page”, right? Wrong! It resends your last message!

    Chat search does not work

    I’ll often search the sidebar chat for something and can’t find the one I need.

    What am I going to do about it?

    I won’t pay a big AI company for this — they already have plenty of money, and I don’t like the politics of any of them.

    I tried looking at the open-source or smaller alternatives, and I’m dissatisfied.

    You have the desktop apps like Jan or AnythingLLM, which aren’t versatile and are unavailable on my phone.

    Web-hosted apps like Open WebUI or LibreChat are bloated.

    I want something self-hostable, on the “cloud”, and lightweight. I’ll use an LLM provider in a data center, because it’s faster and more energy efficient.

    So going to build my own: I’ll only use cheap open-weight models, and vibe-code my ideal LLM harness.

  • Effective use-cases for LLMs

    There’s a lot of talk about the shortcomings of LLMs. They don’t actually reason. They’re expensive, especially when running in a loop. They’re quite slow at doing things.

    There’s a narrow category of use cases that LLMs excel at, one of which is “sifting through the noise”. The noise is everything we have to process to get to what we really want.

    Here are some use cases I haven’t heard about that I’ve enjoyed as a software engineer.

    Searching through Customer Conversations

    A PM colleague uploaded the transcript of every call with our top customers into an Embedding DB. Now their product proposals are deeply backed by evidence. We know 40% of our top customers have mentioned this pain point. The PM also identified a list of eager private beta customers to try out our new feature.

    This is useful when the customer’s problem is abstract. Often, these issues don’t have clear solutions, or those solutions don’t have clear names. That makes filing Feature Requests hard, and organizing/deduping even harder. Before LLMs, your best bet was that someone on your team had enough tenure to have seen this come up enough times, and that they remembered how to find all the links and connections. Now, it’s RAG.

    Going from endpoint alert -> log analysis

    Any large system is going to be operating most of the time in failure mode.

    — John Gall, via Lorin Hochstein, Netflix

    When I’m on-call, one of my responsibilities is to triage failures on API endpoint our team owns. These failures are reported as “high rate of HTTP 4XX/5XX”. Sometimes, it’s noise, like there’s a DB connection hiccup for the pod. Other times, it’s signaling a bug, like customers can’t delete something anymore.

    Triaging is tedious:

    1. The first step is searching for the canonical log lines that mark the specific endpoint with the specific HTTP failure, filtered by time.
    2. Once I find the request that triggered the alert, I search by request ID, to see the request from start to end. Based on the logs, and my source code, I can usually guess what went wrong.
    3. Sometimes the stack trace is compiled JavaScript, rather than Typescript, so the line numbers don’t line up. I have to guess based on the name of the next function call.
    4. I double-check that I’m looking at a representative request. I quickly look at two or three more request IDs to make sure they’re all the same root cause.
    5. For more difficult issues like DB connection timeouts, I’ll see if there’s clustering on the canonical log lines around timestamp, host machine, customer ID. Maybe it’s not specifically my route, but an infra issue.

    All in all, there’s a lot of stuff to sift through. There’s so much judgment required, and I haven’t even found the problem, let alone thought about a solution yet.

    Yet, an agent harness is almost perfect for this. Given some alert and timestamp, point me in the right direction: logs, source code, or clustering. This has cut my triaging time from 15+ minutes to 1-2 minutes per issue. You don’t even need the SotA ($$$$) models. Save your money, use a faster model.

    I published this workflow as a skill for my teammates with the intention of sharing the actual human skill involved. The output names all the queries it tried, categorized into informative or non-informative, with links to dig deeper. I don’t want it to be magical, because I want my teammates to know how to think about triaging. I also want it to be a ramp to independent discovery.

    Shortening Content

    I specifically didn’t call this summarizing because:

    ChatGPT doesn’t summarise. When I asked ChatGPT to summarise this text, it instead shortened the text.

    — https://ea.rna.nl/2024/05/27/when-chatgpt-summarises-it-actually-does-nothing-of-the-kind/

    But despite all that, I still find incredible value in shortening texts! I’ll sometimes get recommended a podcast or video that’s over 1 hour long. Sometimes, I’m hooked within the first 5 minutes. But for technical content, my interest is often buried deep in the video, maybe 30 minutes in for recorded talks. I don’t want to spend that much time figuring out if something is interesting to me, and LLMs greatly help with that.

    In my experience, if there’s enough interesting content in the shortened version, there’s plenty in the unshortened version. One video casually mentioned east-coast vs west-coast programming in the US. Without shortening, I would have stopped watching 19 seconds earlier out of disinterest.

    Transcribing

    Okay, shortening is really useful to me, but how do I get it to work on videos and podcasts? I made myself a little automation that, given a link, will check:

    1. If there are subtitles, download that
    2. If it’s a video, download the audio for transcribing
    3. If it’s audio, transcribe

    Once I transform slow video or audio formats into text, I can summarize!

    I say all this with the caveat that maybe this is a coping skill for my ADHD. Attention is hard for me to maintain, specifically for audio. I literally have a test result saying I’m in the bottom 1% for auditory focus, consistency, AND stamina. These are three separate skills, and I’m statistically awful at all of them. So maybe what matters the most is the ability to transform audio into text, since I’m able to process text much better than audio (though statistically still average).

    Filtering Recruiter Spam

    I get between 1-4 emails per day from “recruiters” or their Agents. It’s quite annoying. They’ll come on weekends, overnight, holidays.

    So I set up an automation that reads my emails every 15 minutes and classifies them as recruiter or not. If it’s recruiter, mark it as read and label it.

    Something small like this has cut down on my email maintenance significantly.

    Sometimes I go through them and try to unsubscribe, but often it’s just personal email spam, not a true mailing list subscription.

    In the past, I’d reply saying I’m not looking for work right now, but the sheer quantity of emails makes me feel like no human will ever actually look at my responses. Just another LLM.

    Instagram Video Search

    This is a dream of mine, but I want to index every video I’ve hearted on Instagram. I want to OCR the subtitles on the screen, transcribe the audio from the video, and object-detect from the thumbnails. I want this because it’s so damn hard to find that one video I liked from 3 years ago. It’s just floating around somewhere in the internet, on Instagram or TikTok. I have no idea how to find it. But I think embeddings can.

    Sure, Google wants to “organize the world’s information”. I appreciate that I can search “horse” on the Apple Photos app and get all the horses I’ve seen. But I want it for my memes, and I’m shocked at how behind Instagram is on this.

    Closing

    That’s all I got.

    I’m still very on the fence about LLMs. I enjoy using my open-weight models. I’m excited to see work on making inference affordable. And I’m terrified of their impact on our economy, social fabric, and individual psyche. But I got some fun goodies out of it.

    Yes, the planet got destroyed. But for a beautiful moment in time we created a lot of value for shareholders.

    Tom Toro, The New Yorker

  • Grand Tetons and Yellowstone 2026

    Grand Tetons and Yellowstone 2026

    I spent 6 nights in Grand Teton NP in a campervan, and one night in Yellowstone!

    Hot takes:

    • Grand Teton is better
    • 6 nights at Teton is perfect
    • 1 night at Yellowstone is more than enough
    • Yellowstone vibes are off
    • It’s hard to dress for — lows of 30F, highs of 70F is super annoying

    We rented a campervan. It’s expensive but nice!

    Grand Teton is amazing

    Overall I highly recommend staying at Signal Summit Campground, it’s our absolute favorite. It’s much smaller, very quiet, out of the way from the freeway, and right by the water. We spent many days/nights hanging out at the Boat Ramp for the campground, watching the sun set over the tetons while meeting deer and foxes.

    Gros Ventre campground was alright. It’s pronounced gro von-ter cause it’s French from the beaver trappers. It’s strangely outside the park.

    I would not recommend Cascade Canyon. We did once hiking from the visitor center to the valley, past Inspiration Point, into Cascade Canyon. It’s so hot and exposed and crowded. Many people do both the hike and the shuttle boat, so the trail is full to the brim with people. The only wildlife we saw was actually on the Jenny Lake Loop portion: a marmot, a moose, and a bear!

    Instead of the Cascade Canyon trail, I’d recommend the Jenny Lake Loop. It’s much quieter, you’re way more likely to run into wildlife like these moose, bears, and deer!

    It’s okay, that’s a cinnamon black bear, the friendly kind. We had bear spray just in case, but we got off the trail and let the bear do its thing. Most wildlife prefer to use human trails because it’s easier. Let them be, and nothing bad will happen (unless it’s a grizzly).

    We also ran into a singular moose at Schwabacher Landing!

    I’m particularly fond of these funky trees whose trunks form a weird hook shape. I’ve never seen anything like this before, but there are many of them, and they are all thriving.

    Overall, quite fun and would 10/10 recommend.

    We did it through Moterra which was alright. Kind of expensive but it’s luxery. We had a popup roof so we could stand, it was nice to cook in a van so we weren’t stuck in the cold. Very spacious compared to Escape Campervans. Pickup and dropoff was easy.

    Jackson Hole is Expensive

    I was surprised. It’s one of the most expensive places in the US to live. Look online, the average home price is $20M, and the taxi/Lyft/Uber from the airport is $60 w/o tip. There was a shuttle at one point, but people are too rich to ride a shuttle, so they cancelled it. A lunch can easily run $40 per person. Gas is easily more expensive than in nanny-state California, at $6/gal.

    I did enjoy Thai Plate. Their massaman curry was peanut-heavy and delicious. It reminds me of my first Massaman curry when I traveled to Thailand under pressure from my then-friend, now-girlfriend. I’m surprised the Beli app works well in that tiny town. I’m even more impressed you can get a Too Good To Go bag! It’s only available from Whole Foods, but I would have snatched it up for our 5 days of camping easily. Totally worth it if the timing is right — pickup is 8pm-9pm.

    Yellowstone Vibes Are Off

    I’ll start with the good stuff. We saw a bunch of wildlife and geysers. Uniquely, we saw pronghorns, osprey, and buffalo.

    My dissapointment is that:

    • we have buffalo in Golden Gate Park here in SF
    • our wildlife encounters in Grand Teton were much more intimate
    • humans kind of suck in this park

    Okay, okay. Yes, we only have like 10 buffalo in SF. But also the only beautiful place we saw buffallo was in Lamar Valley. It’s definitely worth the trek out there to see, and you’ll need a scope or binoculars, but it’s nice to see them roam such an open space. It’s weird to see people starting traffic jams because there’s a buffalo on the side of the road. It sucks for driving, and the buffallo don’t seem happy either. It’s no wonder so many people die from being gored by a buffalo every year. Don’t mess with them.

    All of your wildlife experiences in Yellowstone are going to be from your car. The park is so big, and most of the park is open space. There’s no real incentive for animals and humans to run into each other. In the Grand Tetons, we share the trail and it feels nice for both humans and animals. In Yellowstone, cars are driving 45 MPH, why would you want to be near that?

    I’ll mention I’ve never experienced as much “weirdness” in any other National park as I have with Yellowstone. Grand Teton is amazing. Even Big Bend, Texas was a gem compared to this. For context, I’m a liberal from California. But it was kind of in my face how Yellowstone was so much more old, white, and conservative in a lot of negative ways. Almost every stop in Yellowstone has people prominently showing off MAGA shirts and hats.

    People make a lot more weird comments such as:

    That’s a black bear. It wants to eat humans. But not just any human, not Americans. Only Japanese.

    While I was at a geyser, I had the creative idea of twerking to make the illusion of water gushing out of my butt. It’s a dance technique! I’ve been learning it on my hip hop dance journey since Feb 2025. However, it scared some folks and they asked the ranger “why do you think some people do that?” loudly and right next to me. It’s weaponizing shame to manipulate my behavior. Instead of talking to me, he talked about me so that I could hear. It’s sad because I’ve worked hard overcome my performance anxiety. I won’t let this stop me from dancing, but I NEVER experienced this anywhere else. Sure people might stare or laugh, but that’s part of the fun. The shame inducing manipulative comments are not fun, even when the person asking is laughing. Kudos to the ranger for explaining that geysers are beautiful and everyone expresses their sensations in different ways.

    I’ll end on a lighter note with my favorite shirt: “I’m from Vegas and I’ll only get on my knees for one man: Jesus”

    Also, we genuinely enjoyed our meal at Yellowstone Lake Hotel Diner. We picked it because Fodor’s travel book said it was the only place that had unique food. And I’ll say, really did enjoy it. Food is an asian fusion in the best of ways and it challenged my understanding of crab rangoon. I recommend the Duck Bacon Wontons ($16.95), Sticky Ribs ($21), and Yellowstone Caesar w/ Grilled Salmon — done medium ($20) in that order. I’d pass on the Shrimp and Grits ($33) because it’s a bit bland and straightforward.

  • Organize your Slack channels by “How Often”, not “What”

    Organize your Slack channels by “How Often”, not “What”

    A few weeks ago, I changed my Slack channel sections. I’m now more responsive and engaged, while also feeling less stressed. How? By sorting my Slack channels by urgency, or how often I want to read them.

    What
    Projects
    Team
    Alerts
    Sibling Teams
    Announcements
    SF Office
    Social
    Noisy

    Versus

    How Often
    Read Now
    Read Hourly
    Read Daily
    Read Whenever

    Sorting by “how often” lets me read my most urgent messages first, focusing my energy on what matters to me. Once I feel tired, I stop reading. By focusing on my most urgent and important channels, I hold confidence that I have already taken care of what I need to, reducing my stress.

    A screenshot of my Slack sections. The top consists of my “high priority” sections like Read Now, Threads, DMs, and new channels. I go through my unreads from top to bottom until I am tired or I’m done.

    By framing a channel’s importance through the Eisenhower Matrix, I focus on how I contribute to channels.

    UrgentNot Urgent
    ImportantRead Now / Read Hourly

    I directly answer questions, engage in conversations frequently, or react to them in the real world
    Read Whenever

    I read announcements and keep up-to-date with changes
    Not ImportantRead Daily

    I can push the conversation along by forwarding it to different channels or tagging more appropriate people
    Read Never

    Reading these channels is meaningless, but I sometimes write to them, so it’s annoying to leave.

    This framework is flexible. Your needs and availability will change. Projects go from active development to finished. Social channels go through ups and downs. As these changes happen, you can slide the channel between any category, and it’ll still make sense.

    Misprioritized channels are a source of burnout. Noisy channels in important sections waste time and hide the valuable signal. Important channels in noisy sections are missed opportunities. It’s clearest when you think of some sections like Office, Social, and Project. Intuitively, Project is important. Office is important, but maybe not? Social is less important, but I still want to live a happy life. Yet, I kept finding examples that don’t fit.

    ChannelWhatHow Often
    #sf-sweet-treats
    #sf-nyt-crossword
    #fashion-baddies
    Office or Social – Low/Medium PriorityRead Now — cupcakes get eaten fast, crosswords and photos are organized within 15-30 minutes
    #sf-it-helpdeskOffice – Low/Medium PriorityRead Never — It’s never worthwhile for me to read this channel, but I write to them sometimes.
    Project channels that I’m an active contributor toProject – High PriorityRead Hourly — I’m often answering questions to unblock other people
    Project channels where I’m a passive contributor toProject – High PriorityRead Daily or Read — I want to stay on top of announcements and changes, but I’m not actively contributing

    I conclude that organizing by “what” is pointless.

    Why do we organize by “what”? I think because, by default, Slack suggests Priority, Team, Announcements, and Social, priming us for “what”.

    It’s easy to categorize by “what”. It’s easy to explain, and it’s easy to have icons. But I don’t find it useful.

    How did I start categorizing by “how often”? Is a channel high or medium priority? Just guess. Your gut instinct is probably right. In the worst case, you’re wrong and you slide it up or down. If you’re deeply unsure, put it into Read Hourly or Read Now first. You’ll quickly know if it was the wrong decision. After a couple of wasted moments, slide them down a level. Repeat until the channel stops bothering you.

    I’ve been organizing my Slack by “how often” for almost a month now, and have successfully maintained Inbox Zero for Slack every day. Give it a shot!

  • adding my home electricity uptime to status.href.cat

    adding my home electricity uptime to status.href.cat

    status.href.cat now reports and notifies me if my home power/internet goes down!

    (more…)
  • I modeled for swap.care, a probiotic deodorant.  It works really well!

    I modeled for swap.care, a probiotic deodorant. It works really well!

    A few months ago, I trialed a new probiotic deodorant that uses GMO’ed bacteria to fight odors. It worked REALLY well. I applied the cream once and it worked very well for a week, and still had noticeable improvements to my normal body odor after a month. I did not need to use deodorant at all for a week! Normally, even if I shower in the morning, I’ll still come out stinky and sweaty by 5pm. With Swap, I have no worries even if I’m super active. The sweat doesn’t smell!

    Currently, it’s only available in Japan, but swap.care will hopefully be available in the US soon.

    I think it’ll be particularly useful for people like me who sweat just by walking around the city, and are self-conscious about body odor throughout the day. I’d be excited to try this product backpacking as well.

    Swapを試してから、毎日デオドラントを使うことを気にしなくてよくなりました。

    Since I tried Swap, I no longer have to worry about using deodorant every day.

  • Featured: 18 Reasons Community Porgrams Coordinator!

    If you’re looking for a nice non-profit job running community cooking and nutrition classes in San Francisco, check out 18 Reasons! They’re hiring!

    Here I am, teaching a class of elderly seniors how to balance eating nutritiously with their various medical and health needs.

    https://www.instagram.com/p/DJnIVeds8jy

  • docker compose up -> 401 Unauthorized

    I was trying to set up a new retool instance on docker when I encountered a 401 Unauthorized error:

    $ docker compose up -d
    [+] Running 3/3
     ✘ retooldb-postgres Error failed to resolve reference "docker.io/library/postgres:15...                          0.8s 
     ✘ https-portal Error      context canceled                                                                       0.8s 
     ✘ postgres Error          context canceled                                                                       0.8s 
    Error response from daemon: failed to resolve reference "docker.io/library/postgres:15.12": failed to authorize: failed to fetch oauth token: unexpected status from GET request to https://auth.docker.io/token?scope=repository%3Alibrary%2Fpostgres%3Apull&service=registry.docker.io: 401 Unauthorized

    I had just signed up for a new docker account and had not verified my email yet. Docker will return 401 Unauthorized if your email is not verified.

    I’ve hit this issue a few times before, always after starting a new job. Quite frustrating!

  • Can you solve this AI/Math puzzle and get a prize? I couldn’t.

    I found a poster with a math puzzle that points to a reward website. I couldn’t figure it out, so maybe the prize is still available!

    The paper was taped on a traffic light pole at 15th St and Mission St in SF. It prints a math-ish AI-related formula followed by “.ai/givemeprize” and entices the reader to “come find me”. The SF Armory stands out of focus in the background, previously housing kink.com and Star Wars: The Empire Strikes Back. Both the SF Armory and the telephone pole now sit empty and barren.

    Here it is all typed out:

    prefix(
        [
            3!
            + argmax(
                softmax(
                    [log(2), pi + e]
                )
            )
            + log*(16)
        ] ^ [
            BusyBeaver(4)
            + ⌈H100 flops / A100 flops⌉
            + 1
        ]
    )
    
    .ai/givemeprize

    Stop here if you want to work on the puzzle yourself!

    My Breakdown

    Below is everything I figured out before I got stuck.

    • argmax refers to numpy.argmax returns the index of the maximum in an array
    • softmax is more interesting. Wikipedia’s definition is complicated, but pytorch explains it concisely around normalizing probabilities to sum to 1.
    • log is maybe log2 because of the computer context, maybe log10, but probably loge because numpy and pytorch both assume loge.
    • log* is confusing. I’ve never seen that syntax before. Is that natural log, aka loge? I’m guessing it’s log2 because the argument is 16, which makes a nice and round answer of 4, like the rest of the problem.
    • BusyBeaver(4) is either the maximum shift 107 (likely) or the maximum score 13 (original)???
    • H100 flops and A100 flops are the flops on the H100/A100 Nvidia chip. There are so many variations. But “H100 flops/A100 flops” is surrounded by upper brackets, which is ceil. Thus, I only need a best guess. Running through a few numbers, I get around 3.04 to 3.5, so ceil is 4.
    • prefix is weird, but I’m guessing it’s the integer portion of the number, not the decimal portion.

    Here’s my reduced form:

    prefix(
        [
            6
            + argmax(
                softmax(
                    [0.693147181 , 5.85987448]
                )
            )
            + 4
        ] ^ [
            107
            + 4
            + 1
        ]
    )
    
    .ai/givemeprize

    I don’t need to run softmax, because I know the second number will be bigger and argmax will return its index. Argmax is zero-based, so that reduces to one.

    prefix(
        [6 + 1 + 4] ^ [107 + 4 + 1]
    )
    
    .ai/givemeprize

    Again, reducing by simple math:

    prefix(
        [11] ^ [112]
    )
    
    .ai/givemeprize

    Maybe the answer was 11.ai/givemeprize, but now it 404’s. They’re not based in SF, so I doubt it.

    Open questions:

    That leaves these options:

    BusyBeaver(4) = 13BusyBeaver(4) = 107
    log2prefix(11 ^ 18) = 5559917313492231481.ai/givemeprizeprefix(11 ^ 112) = https://4.3249464815*10^116.ai/givemeprize
    log10prefix( (7 + log10(16)) ^ 18 ) = 28351501028247453.ai/givemeprizeprefix( (7 + log10(16)) ^ 112 )
    loge or lnprefix( (7 + ln(16)) ^ 18 ) = 93742854.ai/givemeprizeprefix( (7 + ln(16)) ^ 112 )

    I considered looking at recently registered domain names. I could filter by domains only containing numbers and under the *.ai TLD. I could hit them all at /givemeprize and see who responds at all and without 404. Unfortunately, that information is behind paywalls.

    I love puzzles, but I’m done with this one. I don’t think I get it. Maybe I missed something.

    The Last Bits

    Someone left the correct answer in the comments, but didn’t show any of the work, which is frustrating cause I want to understand!

    However, folks at lobste.rs guided very kindly and I figured out my mistakes!

    • log* is iterated log. I could have learned this if I used ChatGPT or WolframAlpha instead of Google.
    • BusyBeaver(4) is 13, my less likely answer
    • this makes the exponential 10^18
    • prefix in the context of 10^18 is the SI prefix, like EXAbyte or TERAflops. So the answer here is exa.

    I somehow got a lot more traction on HackerNews way after I posted.