惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

freeCodeCamp Programming Tutorials: Python, JavaScript, Git & More
GbyAI
GbyAI
OSCHINA 社区最新新闻
OSCHINA 社区最新新闻
博客园 - 三生石上(FineUI控件)
美团技术团队
Last Week in AI
Last Week in AI
WordPress大学
WordPress大学
L
LangChain Blog
雷峰网
雷峰网
让小产品的独立变现更简单 - ezindie.com
让小产品的独立变现更简单 - ezindie.com
博客园 - 叶小钗
Engineering at Meta
Engineering at Meta
腾讯CDC
Recent Announcements
Recent Announcements
The Register - Security
The Register - Security
有赞技术团队
有赞技术团队
Blog — PlanetScale
Blog — PlanetScale
博客园 - Franky
博客园 - 司徒正美
The Cloudflare Blog
Google DeepMind News
Google DeepMind News
T
Tailwind CSS Blog
C
Check Point Blog
小众软件
小众软件
V
Visual Studio Blog
V
V2EX
F
Full Disclosure
J
Java Code Geeks
MongoDB | Blog
MongoDB | Blog
罗磊的独立博客
人人都是产品经理
人人都是产品经理
量子位
Apple Machine Learning Research
Apple Machine Learning Research
F
Fortinet All Blogs
Microsoft Security Blog
Microsoft Security Blog
奇客Solidot–传递最新科技情报
奇客Solidot–传递最新科技情报
博客园 - 【当耐特】
博客园_首页
Y
Y Combinator Blog
N
Netflix TechBlog - Medium
酷 壳 – CoolShell
酷 壳 – CoolShell
Stack Overflow Blog
Stack Overflow Blog
Recorded Future
Recorded Future
G
Google Developers Blog
Vercel News
Vercel News
大猫的无限游戏
大猫的无限游戏
Microsoft Azure Blog
Microsoft Azure Blog
U
Unit 42
爱范儿
爱范儿
Jina AI
Jina AI

DEV Community

Authentication Security Deep Dive: From Brute Force to Salted Hashing (With Java Examples) Why AI Systems Don’t Fail — They Drift Spilling beans for how i learn for exam😁"Reinforcement Learning Cheat Sheet" I Replaced Chrome with Safari for AI Browser Automation. Here's What Broke (and What Finally Worked) How Python Borrows Other People's Work The $40 Architecture: Processing 1 Billion API Requests with 99.99% Uptime Vibe Coding: A Workflow Guide (From Zero to SaaS) Most webhook security guides protect the wrong side. The scary part is delivery. Headless CMS for TanStack Start: Build a Blog with Cosmic EU Age Verification App "Hacked in 2 Minutes" — What Actually Happened Comfy Cloud’s delete function does not actually remove files Running AI Models on GPU Cloud Servers: A Beginner Guide Event-driven media intelligence with AWS Step Functions and Bedrock I scored 500 AI prompts across 8 quality dimensions — here's what broke How to Call Google Gemini API from Next.js (Free Tier, No Backend Needed) The Portal Protocol: Reclaiming Human Connection in the Age of AI How to Fix Your Team's Scattered Knowledge Problem With a Self-Hosted Forum Intro to tc Cloud Functors: A Graph-First Mental Model for the Modern Cloud Designing Multi-Tenant Backends With Both Ownership and Team Access I Built a Neumorphic CSS Library with 77+ Components — Here's What I Learned PostgreSQL Performance Optimization: Why Connection Pooling Is Critical at Scale Cómo construí un SaaS multi-rubro para gestionar expensas en Argentina con FastAPI + Vue 3 🚀 I Built an Ethical Hacking Scanner Tool – Open Source Project I Replaced /usage and /context in Claude Code With a Single Statusline A Pythonic Way to Handle Emails (IMAP/SMTP) with Auto-Discovery and AI-Ready Design I Collected 8.9 Million Polymarket Price Points — Here's What I Found About How Markets Really Move EcoTrack AI — Carbon Footprint Tracker & Dashboard Everyone's Using AI. No One Agrees How. 5 self-hosted ebook managers worth trying in 2026 Building Your First AI Agent with LangChain: From Chatbot to Autonomous Assistant Common SOC 2 Failures (Real World) Stop Vibe-Checking Your AI App: A Practical Guide to Evals How to Use SonarQube and SonarScanner Locally to Level Up Your Code Quality Your Next To-Do App Is Dead — I Replaced Mine with an OpenClaw AI Sign a Nostr event in 60 lines of Python using coincurve — no nostr-sdk, no nbxplorer, no rust toolchain ITGC Audit Explained Like You’re in Big 4 Patch Tuesday abril 2026: Microsoft parcha 163 vulnerabilidades y un zero-day en SharePoint Stop scraping everything: a better way to track competitor price changes Listing on MCPize + the Official MCP Registry while routing payments OUTSIDE the marketplace — how I kept 100% of my x402 revenue Building an AI-Powered Risk Intelligence System Using Serverless Architecture Why We Ripped Function Overloading Out of Our AI Toolchain Testing AI-Generated Code: How to Actually Know If It Works SaaS Churn Is Killing Your Business. Here Is What to Do About It (Without a Support Team) The Speed of AI Is No Longer Linear - And Self-Improving Models Are Why How to Implement RBAC for MCP Tools: A Practical Guide for Engineering Teams From Standard Quote to Persuasive Proposal: AI Automation for Arborists I built a CLI that scaffolds complete multi-tenant SaaS apps Axios CVE-2025–62718: The Silent SSRF Bug That Could Be Hiding in Your Node.js App Right Now The dashboard that ended our friendship Data Pipelines Explained Simply (and How to Build Them with Python) The Hidden Cost of AI Systems Nobody Talks About. undefined vs undeclared, and how typeof behaves Switching from file-based jobs to NATS/Kafka in Rust without changing code io_uring Adventures: Rust Servers That Love Syscalls Why Agentic AI is Killing the Traditional Database The POUR principles of web accessibility for developers and designers Quantum Neural Network 3D — A Deep Dive into Interactive WebGL Visualization How To Install Caveman In Codex On macOS And Windows Automation Pipeline Reliability: Why Your Workflow Breaks When Nobody Is Watching I Built an 'Open World' AI Coding Agent — It Works From ANY Folder From Freelancing to Product: A Tech Service Company's SaaS Transformation China's AI Giants: Adding Tencent Hunyuan & ByteDance Doubao to AI University (74 Providers) On the Vibe Coders and Their Lies clerk: Auto-Summarize Your Claude Code Sessions AI Weekly — 2026/04/10–04/17 | The Model Lockdown Is Here, but the Toolchain Is the Real Battleground AI 週報 — 2026/04/10–2026/04/17 模型封鎖潮來了,但工具鏈才是真戰場 Maybe this is how Open-Source apps are born... 🚀 Fine-Tune LLMs with LoRA and QLoRA: 2026 Guide tRPC v11 + Next.js App Router: End-to-End Type Safety Without the Boilerplate ShadCN UI in 2026: Why I Stopped Installing Component Libraries and Started Owning My Components SaaS Billing in React Server Components: Stripe + Supabase Without a Single `useEffect` Join our DEV Weekend Challenge — $1,000 in Prizes Across TEN winners! Submissions Due April 20 at 6:59 AM UTC. Implementing FSRS Spaced Repetition in Flutter + Supabase — Adding Memory Science to an AI Learning App "I Texted My Localhost From the Train — Claude Code Fixed the Bug Before I Got Home" I Built a Sales Prep AI and It Went Deeper Than Expected Design to Code #2: One JSON, Eleven Outputs Solving the 100M-Row Problem: A Summary Table Pattern for High-Volume Push Notification Logs Flutter Web With Wasm: What Actually Changes For Developers I Built 50 Royalty-Free Soundtracks for My Side Project in a Weekend Using AI Music Generation The Vibe Coding Security Checklist: 7 Things to Check Before You Ship Stop Letting Googlebot Guess Fix Your React App's SEO Right Desconstruindo o Streaming do LinkedIn: Como Criar um Engine de Extração de Vídeo de Alta Performance com HLS e FFmpeg (EDA Part-1) EDA (Exploratory Data Analysis) Explained With Real Life — Why Looking at Your Data Is the Most Important Step in Machine Learning Brand Relationship Management at Scale: Our 4-Touch Outreach System for 200+ Brands Why String.fromEnvironment() Might Return an Empty String in Dart JGuardrails 1.0.0 — Hardening Java LLM Apps Against Jailbreaks, Toxicity, and Prompt Injection Plan and Schedule a Full Week of Threads Content From One Claude Conversation Coding Cat Oran Ep3, Five Tables Changed Everything Updated: BFF Pattern I'm done watching freelancers get buried by 200 proposals. So I'm building the alternative. This is my first post BFS Algorithm in Java Step by Step Tutorial with Examples Tracking LLM Pricing Monthly: An Open Dataset for 22 AI Models How We Measure Content ROI on a Comparison Site: Revenue Attribution Without Perfect Data Introducing Nova AI Ops: The AI-Native Operating System for SRE Teams I built a free desktop video downloader for Windows — Grabbit How Talkie OCR Helps Vision-Impaired & Dyslexic Users Read the World Around Them VRCFaceTracking安装和iPhone面捕配置教程,有bug Even CrowdStrike Can't See Your Agents The Automation Gold Rush: What n8n Workflows and Claude Are Opening Up for Developers Right Now
What I Learned Building a Lightweight Local AI Agent
Evgenii Engi · 2026-05-09 · via DEV Community

A Raspberry Pi sitting on top of a Mac mini — both running openLight, both small, both always on.

When I wrote the first post about openLight in March, the project was a 1.0 commit, a Raspberry Pi, and a Telegram bot. I had a Pi running tailscale, a small Matrix homeserver, and I was tired of ssh pi@raspberrypi.local && systemctl status … from a phone keyboard. So I wrote a Go binary that talked to a Telegram bot, kept state in SQLite, and could fall back to a local Ollama model when the rule-based router didn't recognize the request.

Two months later the binary is still ~25 MB, still one config file, still SQLite. Almost everything underneath has been rewritten at least once. The identity I'd write today is shorter than anything in the v0.0.1 README:

openLight is a lightweight operational layer for personal servers, not a generic AI assistant.

That sentence didn't exist in March. It took two months of building, deleting, and re-building to find it. This is the retrospective I wish I had read before starting.

Five moments where it earned its keep

Telegram alert: synapse status check failed, with Restart / Logs / Status / Ignore buttons.

  • A train station, 11pm. Synapse is down on the VPS. I tap Restart on the Telegram alert. It comes back. I don't open a laptop.
  • A grocery line. Tailscale shows yellow. I tap Logs, see a known transient peer issue, tap Ignore for 15 minutes. Done before checkout.
  • A flight. I'm offline. The watch loop runs anyway. When I land, three resolved-incident messages are waiting.
  • A Mac mini at home. It runs Ollama and a few Docker services. CPU > 90% for 5 minutes triggers a watch I'd forgotten about. My own background job is the problem.
  • A friend's homelab. /restart matrix from my couch hits a remote docker-compose service. Same UX as the local Pi.

The architecture below only matters because of those moments.

What changed technically

Routing: from flat to deterministic-first

The original router was flat: try slash commands, try a few regexes, fall through to Ollama, run whatever the model picked. This works for about a week. Then you notice that half your latency is the model warming up on a Pi, the model picks plausible-but-wrong tool names ~10–15% of the time, and you can't separate "I don't know" from "I'm 51% sure, do the thing."

The current router has five layers before the LLM is ever consulted, and the LLM path itself is two-stage:

Router before and after: a flat slash → regex → Ollama path on the left, a deterministic-first cascade with a two-stage LLM classifier on the right.

The semantic layer pays for itself within a week: покажи логи tailscale, show tailscale logs, and tail -f tailscale all normalize to the same skill identifier without ever waking the model. On a Pi, that's the difference between sub-100ms and 2–5 seconds.

Telegram:

The screenshot above is the same /status skill, reached without an LLM call: the Russian phrase normalizes deterministically.

The two-stage split matters because the failure modes get easier to reason about. "Can't pick between service_logs and service_status" is not the same problem as "can't tell whether the user wants services or notes." Splitting the decision splits the latency cost, the error mode, and the prompt size.

CLI hitting the same runtime as the Telegram bot —  raw `skills` endraw ,  raw `watch list` endraw ,  raw `notes` endraw  against agent.test.yaml.

The CLI subcommand wires up the same agent, the same registry, and the same auth as the Telegram path. This is also what the smoke harness drives in CI.

From localhost to named SSH nodes

v0.0.1 could only operate the box it ran on. A Telegram bot is a singleton, so deploying one openLight per host wasn't an option. The model became: one openLight, many nodes, where a node is just a named SSH target in config. Service specs grew a node:vps:compose:/opt/matrix/docker-compose.yml:synapse syntax that resolves to the right backend on the right host.

This forced the services module to become an interface, not a function:

One user-facing command, one allowlist check, one resolver, six backends behind it: local systemd / docker / compose, plus remote variants over SSH.

Six backends behind one skill. The user sees one command. Auditing sees one skill call. The temptation to expose the backend distinction was strong and wrong.

From request-response to a monitoring loop

The biggest behavioral change is watch. v0.0.1 was reactive: I asked, it answered. v0.1.0 added a polling subsystem that holds rules, opens incidents, and initiates messages with inline buttons: Restart, Logs, Status, Ignore.

The interesting part isn't the polling. The interesting part is that alert buttons reuse the existing skill surface. When I tap Restart on an alert, the runtime calls the same service_restart skill I'd call manually. One allowlist check, one audit row, one logging path. There is no separate "automation" surface.

End-to-end incident: alert → Restart button →

I think this is the single design decision I'm proudest of. The fashionable direction in agent frameworks is to give the LLM a sandbox, a shell, and trust. The unfashionable direction — and the correct one for infra — is to make manual and automatic actions go through the exact same validated code path. Automation is a button press, not a permission level.

What changed philosophically

Skills are the only safety boundary

In v0.0.1 I had a mutating_execute_threshold knob — a confidence floor for state-changing actions. By v0.0.2 it was gone, and I'd internalized the rule: the LLM can only choose among already-registered skills, and skills enforce their own allowlists in Go. No threshold, no soft gate. Either there's a Go function the LLM can name, or there isn't.

The model is a classifier of intent, not a holder of permissions. Permissions live in the code that runs after the classification.

Core vs. Optional, made explicit

The README has two lists: core modules (always on) and optional ones (off by default). Every time I added a fun module — vision, voice, browser — I felt a pull to make it on by default, "because the demo is so cool." Three weeks later I'd be debugging why the Pi's memory was full of Playwright. Making "off by default" structural, not a config preference, is how I keep myself from drifting the project into a generic AI assistant.

/skills response showing all groups: Chat, Notes, Memory, Files, Browser, Services, Watch, System, Core, Vision, OCR, Visual watch.

The same registry is exposed to the LLM classifier and to the user's /skills reply. There is no parallel surface.

From a Raspberry Pi project to personal infrastructure

Somewhere during the third or fourth refactor I realized openLight was no longer about a Raspberry Pi. The Pi was just the smallest machine I happened to have. What I'd actually become interested in was the broader category: small, always-on computers running useful local software without cloud-scale tooling and without enterprise infrastructure. A Pi 4 in the closet. A Mac mini M1 on the shelf running local models. A used NUC behind the TV.

These machines have something in common that mainstream infra tooling does not optimize for. They run on residential power. They reboot when the cleaner unplugs them. They have one operator, who is also the developer, who is also the on-call rotation. Kubernetes is not the answer. A Datadog agent is not the answer. What this hardware needs is a small, reliable, observable layer that gets out of the way until it's needed.

The Mac mini in particular changed the project's scope. Once I started deploying to darwin/arm64 with launchd instead of systemd, openLight stopped being "a Telegram bot for Raspberry Pi" and started being a personal-infrastructure agent that happens to use Telegram. The Pi case became the smallest case of a more general thing, instead of the only case.

I don't have a clean term for this category. "Homelab" skews hobbyist; "self-hosted" skews ideological. Personal infrastructure is the working name. It's the layer between "my laptop" and "the cloud," and a non-trivial amount of useful software is going to be built for it.

What I got wrong

The "alternative to OpenClaw" framing. The original README defined openLight by what it wasn't. People who don't know OpenClaw don't care; people who do read it as defensive. Worse, it gave me a permission slip to make decisions by negation. Define yourself by what you are.

"Structured tool calling" on the v0.0.1 roadmap. The right vocabulary, the wrong target. What I needed wasn't more sophisticated tool calling — it was a stronger pre-LLM router. The skill set is small enough that 80% of intents are reachable by deterministic parsing, and the remaining 20% are better handled by classification into an existing skill than by generating a tool call.

Dumping the full skill catalog into the LLM prompt. Early on, the classifier saw every registered skill with full descriptions. This blew the prompt past 4K tokens, slowed routing, and made the model worse — too many near-duplicates. The two-stage classifier fixed it: stage 1 sees only groups, stage 2 sees only skills inside the chosen group. The model's input budget is also a design constraint.

Files: too closed, then too open, then re-gated. Three rewrites of the file skill, each triggered by me almost doing something stupid via Telegram on a tired night. None of those mistakes shipped. They came close enough.

What practice confirmed

Telegram is the right interface for homelab ops. Mobile-native, group-aware, real bot API, already in your pocket. I tried Slack briefly and a web UI for a weekend. Both lost to "I'm at the airport and Synapse is down."

SQLite is enough. Watches, incidents, settings, message history, skill calls — all of it lives in one file with modernc.org/sqlite (no CGO). Backup is cp.

Single Go binary is the right shape. No runtime dependencies, no service mesh, no Helm chart, no Postgres. scp and a systemd unit. Deploys in under a minute.

Local LLMs are good enough for routing. A 0.5B Qwen on a Pi handles the 20% of intents that don't deterministically parse. I don't need GPT-4 to decide whether "show me what's broken" means /status or /watch list.

Allowlists beat permission dialogs. Asking the user "are you sure?" feels safe and is theatre. Forcing the operator to write services.allowed: [tailscale, synapse] in YAML is the actual safety boundary.

Where it's going, and why I think it matters

I don't have a v0.2.0 manifesto. The next things on the list — durable LLM memory, richer filesystem with tracked changes, on-device voice (ffmpeg + whisper-cpp), a read-only browser skill behind a strict allowlist — will all be off by default. If they start drifting toward "generic AI assistant" in code review, they don't ship.

I want to end on something larger than the project, because I think the project is a small data point in a bigger argument.

The dominant story about AI agents right now is enormous: cloud-scale models, autonomous multi-agent systems, infinite tool catalogs, generalized assistants that do everything for everyone. I think that story is going to underdeliver in a specific direction. The most useful AI infrastructure of the next few years is not going to be the cloud agent. It's going to be the small, boring, reliable system that runs close to the operator.

Local-first, because the operator and the hardware are in the same place. Deterministic by default, because the LLM is a great classifier and a poor decision-maker, and infrastructure does not need a poet. Observable, because the operator is also the on-call rotation. Repairable, because something always breaks. Cheap enough to leave running forever, because the moment you can't, you stop running it.

openLight is a single-person project trying to be a small, honest example of that. It's not trying to replace engineers and it's not trying to be smart. It's trying to reduce the friction between an engineer and the small systems they already operate.

Code lives at github.com/evgenii-engineer/openLight. The architecture doc is honest about the seams. The CHANGELOG doesn't oversell. If you run a homelab or a Mac mini or a closet full of small machines and you operate them from a phone, this might be useful. If not, it almost certainly isn't, and I'd rather you know that now than after you've cloned the repo.