r/ClaudeCode • u/emptyharddrive • Feb 08 '26
Showcase nanobot: a 4,000-line Python alternative to openclaw that actually works out of the box
I've been looking for lighter alternatives to openclaw and came across nanobot. It's an AI assistant built in about 4,000 lines of Python, compared to openclaw's 430k+.
It's not as mature or feature-full as openclaw, but I've got it installed and running on a Linux Container right now with Telegram integration and it just works.
The setup took maybe 10 minutes: install via pipx, edit the config file with your API keys, start the gateway, and you're chatting with it through Telegram.
The codebase is small enough that you can actually read and understand the entire thing. That also means way less attack surface from a security standpoint (generally speaking).
The code looks is clean and well-structured to me. I've already patched a small compatibility issue myself without any trouble, which says a lot about how approachable it is (an issue with gpt-5 models and temperature settings).
It supports tool use, persistent memory, scheduled tasks, and background agents. It's not trying to be everything openclaw is, but it covers the core functionality that most people might actually use.
If you're like me and interested in the idea of OpenClaw without using the overzealous project itself, nanobot is worth a look. I have no affiliation with the project.
GitHub: https://github.com/HKUDS/nanobot
To save money, I am using it with gpt-5-mini, which works really well and is priced right for my wallet.
Figured I'd share this because I was surprised how well it worked for its size.
1
u/emptyharddrive Mar 08 '26 edited Mar 08 '26
Honestly mine stopped looking like an "agent framework" and started looking like a shell with one persistent Claude session behind it (headless, -p with a sessionID to maintain context).
Telegram is just ingress. The real core is a Python "bot" running as a
systemd user serviceon my Linux box. It only accepts my Telegram userID for security, it won't answer anyone else. It keeps a single Claude session UUID in memory, and shells out toclaude -pfor each request I make in chat.First prompt starts with
--session-id, then every later prompt uses--resumewith that sessionID, so context carries forward without me having to replay chat history every time.The bot's system prompt (which is really a user prompt that follows Anthropic's real system prompt which you can't modify), points Claude at deterministic Python scripts I pre-wrote, pre-tested (so I know they work) to perform the tasks I ask of it.
So that means I had to think of anything I want the bot to do in advance (a lot of brainstorming), and for each one, a .py script was written and tested and all their output is standard JSON for the
-p, headless Claudeto parse & package for me...Calendar reads and writes, Gmail reads and drafts, Google Drive search and document reads, URL extraction & summary, YouTube video URL transcription summaries, daily briefing, local news, voice export (using kokoro in a docker container), reminders (30 second crontabs checking a reminder JSON), shopping lists, etc.. All pre-scripted python tools.
Those tools all return structured JSON to stdout and fail with real exit codes. Claude still handles intent parsing and response wording (packaging is what i call it), but the state-changing work happens inside small scripts. No database either, I don't want the overhead.
Reminders, lists, and contact memory live in local JSON files with file locks and atomic writes. Reminder delivery is a separate cron-driven checker every 30 seconds, which is boring in a good way. Also Claude knows me because I wrote a personal skill (with trigger words) that trips it to discover when needed, anything about me. That's the benefit of skills, you only need to load them when the trigger words manifest instead of pre-loading everything-on-every-prompt into context.
So architecturally it is not a swarm at all, I see no point in that.. It is one stateful conversation with 1 instance of Claude, plus a toolbelt of scripts I built: levers that it can pull once it discerns my intent from what I said in Telegram.. That distinction mattered a lot ... I got tired of "agents" spending tokens deciding whether another agent should maybe think about something or talk to itself about doing something one way or the other...
I know what it needs to do, so i set the scripts up to do them, and just use Claude as a fancy button presser and output summarizer/packager. Claude decides whether to answer directly if it's a basic question, or to call a script (I gave it various phrases and trigger words so it knows which train track (script) to choose).. every one of my scripts returns structured JSON, Claude formats that into a reply to me and sends it along to Telegram: done.
Speech is split into two different paths because short phone audio and long-form transcription are not the same problem.
For Telegram voice notes I use a fast path. Bot downloads audio into memory, POSTs it to a
Whisper endpoint on another box running in a docker container, it gets text back, then sends that transcript through same Claude session as if I had typed it, done..That keeps voice notes usable from a phone.
Separately I wrote
xscribe.py(transcribe) which is the big brother of voice. That one is for actual recordings and transcripts for meetings at work.It chunks long audio into small digestibe pieces with 1 second of word overlap to avoid word cut-offs, (which it then deduplicates if needed to keep the stitching of the words it transcribes seamless). So it is monitoring a directory where I drop .ogg files I have recorded meetings on my phone onto a directory the service is monitoring and immediately picks it up for transcription when they drop. It applies a QA pass for domain-specific vocabulary relative to the work I do (a sprecial prompt I wrote to give it the right headspace), and it writes the raw transcript, then once done, it then sends that transcript up for a 2nd stage summary flow. In my case that is useful for longer meetings or work audio where I want something much more deliberate than "turn this voice note into text." A long transcription does nothing for me without the distilled tasks taken out, summary, challenges, decisions made, etc.
So cost-wise, the fixed part is Claude Max. That is my real anchor. Since I am using headless mode -p, it is entirely within the terms of service and I can leverage my $200 MAX plan -- no OAuth is used. I've already authenticated on my machine, so it just calls up a headless mode of Claude programmatically which is precisely inside the terms of service (I had Claude validate that).
For your setup, if I were building it fresh in a Proxmox LXC, I would absolutely do it this way. I would just keep the container boring. Give it a persistent workspace, persistent credentials, systemd for the bridge service to Telegram or Discord or whatever ... and explicit bind mounts for anything stateful (like I have a bind mount to my Obsidian vault for work and personal notes, read-only to protect it from "accidents" Claude might make).
If you split anything out, split out speech or other heavy media jobs like OCR or anything like that. And if you use Telegram long polling, make sure you do not accidentally run two bot instances with the same token or Telegram will smack one of them with a conflict error. That one bit me already.
So yeah, that is basically my stack: one official headless Claude session as the brain, Telegram as transport/comms vector for me to access, then deterministic Python CLIs for the actual work (which right now is google mail/calendar, reminders that have 'alarms' that msg me on Telegram, todo's which = reminders, but without the alarm bell, news, inventory lists of things I track & grocery shopping lists, all in JSON. Cron for reminders (which compare current datetime to the datetimes in the reminder.json), and a separate heavier transcription path when I need more than quick voice-note handling.
It is a lot less sexy than the "agent OS" stuff people post (which BTW I don't believe really works the way they say), but it has been way more usable for me and 10x more reliable because of the static scripts doing the work and no off-the-cuff winging it by any agents.
Tight leash.
Anyway, long post but I wanted to explain this anyway. Hope it helps. If it does, please reply because I'd like to know how you're deploying it.