Digital Resource-Optimizing Neural Gadget for Overthinking — a self-directed, autonomous maker-agent for the Rock Pi 4C+ (RK3399) running headless Debian.
Set it and forget it. It wakes up on a loop, decides its own little project — a browser game, a generative image, a handy script, a live hardware dashboard, a short research note — builds it with real tools, and writes a journal entry you can read later from a web dashboard. It can ping you on Discord or blink an LED you wire to a GPIO when it makes something good. It runs on free cloud LLMs (with an optional local model as a never-fail fallback), and it is wrapped in serious, tamper-resistant safety rails so you can actually trust it with the keys.
Philosophy (from the project spec): "Local Autonomy, Global Isolation."
No Linux expertise needed. If you can flash an SD card and copy-paste, you can do this.
0. What you need
- A Rock Pi 4C+ (the 4 GB model is best; 2 GB works with a smaller brain; 1 GB is tight).
- Storage flashed with Debian (eMMC or NVMe SSD strongly preferred over a microSD).
- The Pi on your network, and the ability to SSH into it.
- (Optional, makes it smarter) free API keys — links in the provider table below.
1. Get the code onto the Pi (either works):
git clone <your-fork-url> drongo && cd drongo # preferred (enables self-update)
# — or — download the ZIP, unzip it, and `cd` into the folder2. Install (one command):
sudo ./install.shIt shows a banner, then asks a few quick setup questions — install a local fallback
model?, disable the desktop to free RAM?, install the retro toolchain?, build the local
image generator? — each with a sensible default, so you can just press Enter through
them. Then it checks your system, sets everything up, and ends with a health check.
When it prints ✓ DRONGO is installed and running it's live — running on the free
cloud providers once you add a key in the wizard (step 4), or on a local model if you
opted into one. (Prefer flags? --local --strip-desktop --retro --imggen --model NAME
pre-answer the questions, and --yes accepts every default without prompting.)
3. Did it work?
sudo drongo doctorYou want VERDICT: ✓ READY. Then open the dashboard in a browser:
http://<your-pi-ip>:8080/ and watch it build things. Check in whenever you like.
4. Setup wizard (runs automatically at the end of install): it asks for a Discord
webhook, an LED pin, and any free API keys — press Enter to skip anything, and it
restarts DRONGO for you. Re-run it any time with sudo /opt/drongo/configure.sh. (You
can also hand-edit /etc/drongo/drongo.env — see Alerts.)
Changed your mind?
sudo /opt/drongo/uninstall.shremoves it cleanly (keeps your data unless you add--purge). Jump to Troubleshooting if anything looks off.
| Capability | How |
|---|---|
| Decides what to do | Self-directed ideation each cycle, avoids repeating itself |
| Writes & runs scripts | Sandboxed shell + file tools, each project kept in its own projects/<name>/ folder |
| Makes images | Keyless, free generation (Pollinations) into a gallery; optional local generator (OnnxStream) |
| Builds games / dashboards | HTML/JS written to the workspace, served by the dashboard |
| Senses its hardware | Scans i2c/spi/1-wire/thermals/USB/cameras, builds dashboards |
| Learns from its work | Indexes its own repo + past projects, auto-harvests reusable skills, retrieves relevant knowledge (RAG), downloads skill packs, and curates a fine-tuning dataset |
| Uses YOUR docs as truth | Upload reference docs (API/specs/datasheets) — indexed with FTS5 and searched/injected when it builds, so it stops guessing |
| Extends via MCP | Connect Model Context Protocol servers (stdio/http) — their tools become the agent's tools, sandboxed like everything else |
| Talk to it & steer it | A Chat tab — ask what it's doing or tell it what to build next, any time (even mid-project); teaching it facts saves them to memory |
| Shows its token usage | Per-provider tokens (in/out) + totals, charted live, with the latest call's count by the thinking stream |
| Installs its own packages | Requests apt packages; a scoped root helper installs the ones you allow (policy + hard allow-list) |
| Updates itself | Request-based, applied by a privileged root updater with rollback |
| Alerts you | Discord webhook, a GPIO LED you wire up, ntfy, or any command — pick any combo |
| Stays alive | systemd + in-agent watchdog + external observer + SoC hardware watchdog |
| Can't hurt itself/you | Immutable safeguard, OS sandbox, resource caps, crash-loop safe mode |
The RK3399 is a 6-core CPU (2×A72 + 4×A53), up to 4 GB RAM, no usable GPU/NPU for LLMs — so DRONGO's brain is the free cloud providers below, and the heavy CPU work goes into its projects (fractals, sims, native code), not inference.
Storage: use eMMC or an NVMe SSD, not a microSD. The agent writes a lot
(SQLite, logs, artifacts) and SD cards die from that. Put /var/lib/drongo on the
fastest storage you have.
Optional local model: want an offline never-fail fallback for when the cloud
tiers are all rate-limited? It's off by default (~2 GB RAM) — add it with the
installer's prompt or --local. Full guide: docs/local-model.md.
Providers wired in (add the keys you want, skip the rest — a provider with no key is silently skipped). Tried top-to-bottom, free first, paid Claude a capped last resort (the optional local model sits below them all):
| Provider | Cost | Get a key |
|---|---|---|
| Cerebras | free, very fast | https://cloud.cerebras.ai |
| Groq | free, fast | https://console.groq.com |
| Google Gemini | free tier | https://aistudio.google.com/apikey |
| Mistral | free tier | https://console.mistral.ai |
| OpenRouter | free models | https://openrouter.ai/keys |
| Pollinations | free (needs a free pk_ token — no card) |
https://auth.pollinations.ai |
| Claude (Anthropic) | paid — Haiku 4.5, last-resort, capped at 200 calls/day | https://console.anthropic.com |
| Local Ollama (optional, off by default) | free, no limits | --local at install, or flip it on in the config |
Claude uses the native Anthropic API (not an OpenAI shim) and needs
pip install anthropic (already in requirements.txt). It's reached only when
every free provider is simultaneously rate-limited, so spend stays minimal; raise
daily_limit or set model: claude-sonnet-4-6 / claude-opus-4-8 in the config
if you want more of it. More free options (GitHub Models, NVIDIA NIM, Pollinations)
are commented in config.example.yaml.
Heads-up on keyless: there's no longer a reliable no-key text provider — Pollinations (once anonymous) now requires a free
pk_token for text generation. So "free" here means free tier with a free key, not no key.
Providers can be added, removed and re-ordered live from the dashboard (Control → Settings), and the router backs off and auto-retries a failing provider (429 / 5xx / 404) instead of wedging — so a retired model id no longer stalls the agent until you restart it. Gemini defaults to
gemini-flash-latest, which tracks Google's current Flash model so it won't 404 when a version is retired.
Cloud-only, the 4 GB is comfortable (the agent is capped at MemoryMax=1200M).
Running a local model? Add zram for headroom — see docs/local-model.md.
Five nested layers. The agent sits unprivileged at the centre and cannot reach outward; each outer layer is owned by root and recovers the one inside it.
1 SoC hardware watchdog (/dev/watchdog) kernel/system wedged → board reboots
2 External observer (root timer) crash-loop → rollback + alert
3 systemd sandbox + cgroups + WatchdogSec hang / OOM → kill + restart, capped CPU/RAM
4 safeguard.py (root-owned 0444) denylist + integrity self-check; tamper → refuse
5 DRONGO agent (unprivileged 'drongo') sandboxed to /var/lib/drongo only
agent/safeguard.pyis the conscience, in its own file, installedroot:rootmode0444. The agent runs asdrongoand physically cannot modify it (the OS blocks the write;ProtectSystem=strictmakes/opt/drongoread-only to the process). The file also verifies its own SHA-256, owner and permissions on every shell call and, in strict mode, refuses to run if anything is off (fail closed).- Self-update can't let the agent rewrite itself. The agent only requests
an update (drops a marker); the root
drongo-updateservice pulls from your trusted git remote, syntax-checks, re-seals the safeguard, restarts, and rolls back automatically if the new code won't start. - Crash-loop safe mode. If the agent restarts too many times in a short window it boots into SAFE MODE: shell and self-update disabled, long sleeps, and it alerts you — instead of thrashing the box.
- The external observer (independent, root, stdlib-only so it survives a broken agent) restarts a wedged agent, rolls back a crash-looping one to the last-known-good commit, watches temperature/load/disk, and — only if you opt in — reboots the host as an absolute last resort.
- Running code can't take the box down. Everything the agent runs — its own
shell, project ▶ Run, live-dashboard backends, the local image generator — is
launched in its own process group (a timeout kills the whole tree, so a
forked server/worker can't orphan and keep eating RAM) with per-process
rlimits (address space, CPU, files, pids, file size). The systemd cgroup is
the outer cage:
MemoryMaxplusMemorySwapMax=0, so a memory-hungry run is OOM-killed instantly instead of thrashing the SD-card swap (which is what silently locks up a Pi — the box goes unresponsive while PID 1 stays alive, so no watchdog fires). Logs rotate and the SQLite WAL is size-bounded, so nothing fills the card. - No blind hardware probing. Actively scanning I2C on this SoC can hang the
whole board (the PMIC/RTC live on the bus). The agent discovers hardware
passively (sysfs + the
discover_sensorstool), the shell denylistsi2cdetect/i2cset/i2cget, and its build rules forbid address-probing loops. If you want it to talk to a specific sensor, give it the exact bus + address.
# 1. Flash Debian (Armbian/Radxa) to eMMC/NVMe, boot headless, SSH in.
# 2. Get the code and run the installer as root — it asks a few setup questions:
git clone <your-fork-url> drongo && cd drongo
sudo ./install.sh
# You'll be asked (each with a default; Enter accepts it):
# • local Ollama model • disable the desktop to free RAM?
# • install the retro toolchain? • build the local image generator?
# Prefer non-interactive? Pass flags to pre-answer, --yes to take all defaults:
# sudo ./install.sh --yes --strip-desktop --retroRetro / 8-bit dev (optional): answer yes to the retro prompt (or pass
--retro, or runsudo ./system/retro-toolchain.shany time). It installs sdcc, z88dk, CPCtelera and pasmo so the agent can build for the Amstrad CPC, ZX Spectrum and Z80 (incl. SymbOS assembly). z88dk + CPCtelera compile from source, so it's a heavy, best-effort step — it won't break the base install, and you can re-run it (on failure it saves the build log to/opt/retro/*-build.logso the error is recoverable).
Local image generator (optional): answer yes to the image-gen prompt (or
--imggen, orsudo ./system/image-gen.sh) to build OnnxStream into/opt/imggen. It's slow on a Pi — the keyless cloud generator stays the default; set imageprovider: localin Settings once it's built.
The installer (see install.sh) does everything: packages, the
drongo user, /opt/drongo (root-owned) + /var/lib/drongo (agent-writable),
the venv, (optionally) Ollama + a local model, locks the safeguard to 0444 and
seals its hash, arms the SoC hardware watchdog, and installs/enables all the
systemd units.
Then add your keys (and optionally a Discord webhook — see Alerts):
sudoedit /etc/drongo/drongo.env # CEREBRAS / GROQ / GEMINI / MISTRAL / OPENROUTER (+ ANTHROPIC_API_KEY for Claude)
# and DISCORD_WEBHOOK_URL for alerts
sudo systemctl restart drongo drongo-webYou need at least one provider now — the free cloud tiers are the default brain (the local model is optional). Add a free key above, or install a local floor with
sudo ./install.sh --local.
Check on it:
journalctl -u drongo -f # live log
sudo drongo doctor
# Dashboard: http://<pi-ip>:8080/- OS: flash Debian, then free RAM by disabling (not removing) the GUI:
sudo systemctl set-default multi-user.target+sudo systemctl stop display-manager. Don'tapt purge/autoremovethe desktop on a vendor SBC image — it can pull out hardware/firmware packages. (Sensors/GPIO/I2C come from the kernel + device tree, so a headless box still has full hardware access.) - User: create a system user
drongowithnologin; add it toi2c,gpio,spi,videogroups so it can read sensors. - Layout: code →
/opt/drongo(chown root:root), runtime →/var/lib/drongo(chown drongo), config →/etc/drongo. - Python:
python3 -m venv /opt/drongo/.venv && .venv/bin/pip install -r requirements.txt. - Seal the guard:
python -m agent sealthenchmod 0444 agent/safeguard.py*andchown root:rootthem. - Ollama (optional — skip for cloud-only): install,
ollama pull qwen2.5:3b-instruct, and set thelocalproviderenabled: truein the config. - Hardware watchdog: add
RuntimeWatchdogSec=20sto/etc/systemd/system.conf. - Services: copy
systemd/*.{service,timer}to/etc/systemd/system/,daemon-reload, enabledrongo,drongo-web,drongo-observer.timer,drongo-update.timer,drongo-pkg.timer.
Most of this is now in the dashboard at http://<pi-ip>:8080/ (password-protected,
LAN-only) — a "command centre" styled after desktop agent apps: a left nav rail,
a top bar with always-visible host vitals (CPU · RAM · temp) and today's token
meter, and a command palette on ⌘K / Ctrl-K to jump to any view or run any action.
It's a dark cockpit; the accent colour is switchable (amber/mint/ice/synth) from the
rail. The rail collapses to a hamburger on phones.
- Activity — the default view, a three-pane live cockpit: the live-thinking stream (everything it's doing, untruncated) down the middle, and an inspector on the right showing what it's building now, a mini latest-requests list (tokens in→out), and host stats. This is where you watch it work.
- Requests — every LLM call, metered per request: time · purpose (ideate/plan/execute/critique/chat) · provider·model · tokens in · out · total · latency · status. Plus summary tiles (calls, in/out, in:out ratio), a per-provider in/out token chart, and the aggregate usage table. Full token observability.
- Chat — talk to DRONGO and steer it, any time — even while it's building. Multi-turn (it remembers the conversation), markdown replies with code blocks, a provider picker (auto/router-order or a specific provider), per-reply provider + token counts, copy/regenerate/clear. (Watch it think on the Activity tab.) It can act on you: "build X next" queues that project, a standing preference becomes its mission, and teaching it a fact saves it to memory. See Chat & steering.
- Projects — everything it built (HTML games/dashboards open in a click), each with tags and a 🔧 Fix this button — flag a broken one and the agent works it before starting anything new.
- Gallery — every image it has generated (PNG/PPM), in a lightbox.
- Files — browse the agent's workspace, view/run files, see its package requests, and set the Install policy (see Letting it install packages).
- Brain — steer it and see everything it has learned: suggest its next project, set its standing mission, a knowledge-base summary (repo files indexed / skills / notes / lessons / training examples), saved skills (code view + delete), and notes and lessons you can add, edit and delete — so when the agent learns something wrong, you can correct or purge it (it reuses these when it builds). Import a skill by pasting JSON or downloading a pack from a URL, and download the curated training dataset (JSONL). Plus a raw memory browser — every key in its long-term store, inspectable (API keys masked) and deletable, so you see and control exactly what it remembers.
- Settings — Run-now / Pause / Resume / Stop / Restart and full control over everything: API keys, add / remove / re-order LLM providers, per-provider enable/model, hardware scan, Discord / ntfy / LED, personality & interests, and all the cooldown/loop timers. Saved settings live in the agent's DB and apply on the next restart ("Save & Restart" does both).
- Help — an SSH/admin cheat-sheet (where the key files live, the common commands).
The dashboard template lives in agent/dashboard.html (rendered by
the Flask app) if you want to tweak the UI.
Note: the dashboard is plain HTTP on your LAN (behind the password), so keys you type into Settings cross the LAN in clear — same as the login itself. Fine for a trusted home network; if you don't want keys in the browser at all, keep using
/etc/drongo/drongo.env+sudo drongo configure. Keys are stripped from the agent's own shell so a prompt-injected script can't read them.
| You want to… | Do this |
|---|---|
| See/launch what it built | Dashboard → Projects (HTML opens in a new tab) |
| Pause / resume / restart | Dashboard → Control (or touch …/workspace/PAUSE, remove to resume) |
| Send a broken project back for fixing | Dashboard → Projects → 🔧 Fix this |
| Delete a project (removes its files too) | Dashboard → Projects → 🗑 Delete |
| Run a cycle right now | Dashboard → Control → ▶ Run a cycle now |
| Watch it live | journalctl -u drongo -f |
| Steer its interests | edit interests: in /etc/drongo/config.yaml, restart |
| Force a code update | sudo systemctl start drongo-update |
| Check health/guard | sudo drongo doctor |
| Wipe ALL projects + history (keeps your settings/keys) | sudo drongo reset |
The agent only pushes an alert when it finishes something with artifacts (set
alerts.notify_every_cycle: true for a ping every cycle).
Give the agent external tools via the Model Context Protocol. On the Brain
tab, add an MCP server — stdio (a command like npx -y @modelcontextprotocol/ server-filesystem /path, needs Node) or http (a URL). Its tools become
mcp__<server>__<tool> that the ReAct loop can call like any built-in; test a
server from the dashboard to see the tools it exposes before relying on it. Servers
connect at agent startup (add one → restart).
The client is dependency-light (stdlib JSON-RPC + requests, no heavy SDK). An MCP
server runs inside the agent's own sandbox (unprivileged drongo, no sudo) and
gets a clean environment, so it's no more privileged than the shell tool and never
sees DRONGO's LLM keys. MCP tools are disabled in safe mode.
Upload documentation the agent should treat as authoritative — API references,
datasheets, hardware specs, your own notes — on the Brain tab (or scp a folder
into runtime/docs/). Text is chunked and indexed with SQLite FTS5 (BM25
ranking, no embeddings/torch, so it's light on the Pi). The agent then:
- searches these before guessing (a
search_docstool), and - gets the most relevant passages injected into the build context when it starts a related project, marked as the source of truth.
Manage it all from the browser — upload (.md/.txt/.py/.html/.json/.csv/…; binaries
skipped), list, delete, and a live search box to test retrieval. Uploads are capped
at 32 MB to spare the SD card.
The Chat tab is a conversation with DRONGO itself — not a raw model window. It's answered by the dashboard process, separate from the agent loop, so it replies instantly even while the agent is mid-project. Beyond answering, it can act on you:
- "build a snake game next" → queued as its next project (jumps the loop's queue).
- "focus on retro stuff from now on" → becomes its standing mission (biases ideation).
- "remember I prefer tabs over spaces" → saved to memory as a note it retrieves later.
So the same box lets you ask what it's doing and steer where it goes — and teaching it things is a genuine learning channel (chat facts land in its knowledge base). Token usage for every call is metered per-provider and charted on Home.
The agent can't sudo, and its sandbox blocks it from touching the system — so when it
needs an apt package (a compiler, a C library, a CLI tool) it requests one, and a small
root helper (drongo-pkg, on a 2-minute timer) installs it only if your policy allows.
The helper never runs an arbitrary command — only apt-get install of a validated package
name (no options, no paths, no local .deb) — so it can't be tricked into arbitrary root.
Two allow-lists decide what's permitted (a package installs if either matches, or the
mode is auto):
- Dashboard list — Files → Install policy.
manual+ an allow-list you edit live in the browser, orauto(install any valid package it asks for). Convenient, but editable by the same user the agent runs as, so treat it as a soft control. - Hard list —
/etc/drongo/pkg-allow.conf, root-owned so the agent can't touch it. One package/glob per line; edit it over SSH. The dashboard shows it read-only. This is the tamper-proof baseline.
Default is manual with an empty list, so out of the box it installs nothing until you
allow something. Watch it work with journalctl -u drongo-pkg -f.
DRONGO accumulates a knowledge base it reuses over time — the realistic form of "self-improving" on a Pi (it can't train a model on a 4 GB CPU board, so instead it learns by retrieval and curates a dataset you can fine-tune off-box):
- Starter knowledge — it ships bootstrapped: ~9 reusable starter skills (PPM/image writers, a Mandelbrot renderer, Game of Life, a canvas-game template, the live-dashboard pattern, a CLI template, native-C + retro/Z80 patterns), plus environment notes and best-practice lessons — seeded on first boot so it knows a lot day one.
- Skills — when a project finishes it auto-harvests one reusable code snippet into
its library. Import your own on the Brain tab by pasting
{"name","description","code"}JSON, or download a skill (or a{"skills":[…]}pack) from a URL — stored, never auto-run. - Notes & lessons — research findings and one-line takeaways it saves as it works, plus anything you teach it in Chat (facts you tell it are saved and retrieved).
- Its own repo — at boot it indexes its own code + docs (
agent/*.py,system/*.py, the docs) so it can answer "how does my own X work?" — the repository is its first-class context. - Retrieval (RAG) — before each build it pulls the most relevant of all of the above,
plus its past projects, back into context (lightweight, embeddings-free, so it's light on
the Pi). The agent can also search it on demand with the
recall_knowledgetool. - Training dataset — every finished project appends a chat-format example (task → the
code it produced) to
workspace/dataset/train.jsonl. The Pi curates the corpus; when you want an actual fine-tuned model you run training elsewhere (a rented GPU / a cloud fine-tune API) and point a provider at it. Download the JSONL from the Brain tab.
No phone needed. Enable any combination of channels in /etc/drongo/config.yaml
under alerts: — they all fire together. The root observer/updater also use Discord
(and ntfy) to warn you about crash-loops, rollbacks, and host health.
Discord (easiest):
- In Discord: Server Settings → Integrations → Webhooks → New Webhook, pick a channel, Copy Webhook URL.
- Put it in
/etc/drongo/drongo.env:DISCORD_WEBHOOK_URL=https://discord.com/api/webhooks/...(and the same line in/etc/drongo/observer.envasDRONGO_DISCORD_WEBHOOK=...). alerts.discord.enabled: true(it already is in the example), thensudo systemctl restart drongo.
LED on a GPIO pin:
- Wire it: GPIO line → ~330 Ω resistor → LED (long leg / +) → LED (−) → a GND pin.
- Find which
gpiochipand line offset your pin is:gpioinfo(from thegpiodpackage the installer adds). The RK3399 exposesgpiochip0–gpiochip4; the number you want is the line offset on that chip, not the board pin number. - Set it in
config.yaml:alerts: led: enabled: true chip: /dev/gpiochip0 line: 17 # <- your line offset from gpioinfo active_high: true # false if the LED is wired active-low
sudo systemctl restart drongo. It blinks 3× on a normal alert, 6× on urgent. (Needspython-periphery, already inrequirements.txt.)
Quick test:
sudo systemctl restart drongoand watch — or trigger one cycle withsudo drongo once. Thecommandchannel can run any script on an alert (it getsDRONGO_ALERT_*env vars) if you want to drive something more exotic.
Designed so only you, on your LAN, can reach it — and nothing from the internet can.
What listens on the network (the whole attack surface):
| Service | Port | Exposure |
|---|---|---|
| Dashboard | 8080 | Password-protected (HTTP Basic) + LAN-only (kernel-enforced). No internet. |
| Ollama (model) | 11434 | Only if you enabled the local model. localhost only (OLLAMA_HOST=127.0.0.1) — never on the LAN. |
| SSH | 22 | Yours, managed by you. The optional firewall limits it to the LAN. |
| The agent / observer / updater / pkg-installer | — | No listeners. They only make outbound calls. |
How "only me" is enforced (defence in depth):
- Password — the installer auto-generates a strong
DRONGO_WEB_PASSWORD(shown at the end of install and stored in/etc/drongo/drongo.env, mode0600). The dashboard asks for it (log in with any username). No password ⇒ it refuses the LAN and binds to localhost only. - Kernel LAN-lock —
drongo-web.servicesetsIPAddressDeny=any+ allow only private ranges, so the kernel drops any non-LAN connection before it reaches the app — even if your router accidentally forwards the port. - Optional IP allowlist — set
DRONGO_WEB_ALLOW="192.168.1.50"(or a CIDR) indrongo.envto let in only specific machines. - Optional firewall —
sudo ./system/firewall.shadds a default-drop inbound nftables ruleset (SSH-safe; LAN-only SSH + dashboard). Run it if you want the box invisible from the internet at the packet level too.
Stopping the agent attacking outward (if a web page tries to prompt-inject it):
web_fetchhas an SSRF guard — it only fetches public http/https addresses and refuses localhost / LAN / link-local / cloud-metadata IPs, validating every redirect hop. So it can't be tricked into hittinglocalhost:11434, your router, or other LAN devices.- The
shelltool runs as the unprivilegeddrongouser inside a systemd sandbox (read-only/opt, writable only under/var/lib/drongo), behind the safeguard denylist.
Want it tightest? Set web.host: 127.0.0.1 in the config and view the dashboard through an
SSH tunnel: ssh -L 8080:localhost:8080 <pi> then browse http://localhost:8080/. Nothing is
exposed on the LAN at all.
Honest notes: the dashboard's controls sit behind the password + kernel LAN-lock, and they only ever flip DB flags or write files the agent already manages (add/remove providers, set the package policy, import a skill, flag a project) — never arbitrary root — so a same-LAN request's blast radius is small. The SSRF guard resolves DNS at fetch time — a determined DNS-rebinding attacker is out of scope for a home maker box. And whoever can SSH to the Pi controls it — protect SSH (keys, not passwords) as you would any server.
The agent runs from /opt/drongo (root-owned), not from your clone — so
git pull alone won't reach it. Deploy your pulled changes with:
cd ~/drongo
git pull
sudo ./update.sh # syncs code to /opt/drongo, re-seals the safeguard, restartsupdate.sh is the fast path (no apt / no model re-pull). Re-running
sudo ./install.sh also deploys, but does the full setup. (The root auto-updater
only works if /opt/drongo's git remote is reachable as root — with a private
SSH-key repo that's usually not the case, so update.sh is the reliable path.)
Don't want to hand a cloud search provider your queries? Run SearXNG
— an open-source metasearch engine that aggregates real results from Google/Bing/
DuckDuckGo/etc., exposes a JSON API, and needs no API key. DRONGO uses it
before any cloud provider when SEARXNG_URL is set.
The easy way — let the installer do it:
sudo ./install.sh --searxng # at install time
# — or, on an already-installed box, just the search piece:
sudo bash /opt/drongo/system/searxng.shThat installs Docker (if missing), runs SearXNG on 127.0.0.1:8888 with the JSON
API pre-enabled, verifies it answers, and (via --searxng) sets SEARXNG_URL for
you. The plain installer also offers it as a yes/no question. Then restart:
sudo systemctl restart drongo drongo-web.
Manual / on a spare LAN box (recommended if the Pi also runs a local LLM):
docker run -d --name searxng --restart unless-stopped -p 8888:8080 \
-v /etc/searxng:/etc/searxng searxng/searxng:latestEnable the JSON API (off by default) and turn off the bot-limiter — put this in
/etc/searxng/settings.yml, then docker restart searxng:
use_default_settings: true
server:
limiter: false # let a non-browser client (DRONGO) query the API
search:
formats: [html, json]Point DRONGO at it: set SEARXNG_URL=http://<host>:8888 in /etc/drongo/drongo.env
(the wizard sudo /opt/drongo/configure.sh asks for it), then restart the services.
Test it: curl 'http://127.0.0.1:8888/search?q=rk3399&format=json' should return
JSON with a results array. If it 403s, JSON isn't enabled (or the limiter is on).
If you enforce egress filtering, SearXNG itself needs to reach the upstream engines.
First move, always: run the doctor — it tells you in plain English what's wrong.
sudo drongo doctor| Symptom | Likely cause → fix |
|---|---|
doctor says no LLM answered |
No usable provider. Add at least one free key to /etc/drongo/drongo.env (local is optional/off by default), or add a local model: sudo ./install.sh --local. Then sudo systemctl restart drongo. |
Dashboard won't load at :8080 |
Service not up, or wrong IP. systemctl status drongo-web; find the IP with hostname -I. |
| Dashboard asks for a password | That's intentional. It's in /etc/drongo/drongo.env (DRONGO_WEB_PASSWORD); log in with any username. Change it there, then sudo systemctl restart drongo-web. |
| Dashboard only works on the Pi itself, not other devices | No password set ⇒ localhost-only. Set DRONGO_WEB_PASSWORD and restart drongo-web (the installer normally does this for you). |
Agent keeps restarting / systemctl status drongo shows failed |
Read journalctl -u drongo -n 50. If it's a safeguard error, the installer's seal step didn't finish — just re-run sudo ./install.sh. |
| SAFE MODE in the logs | It restarted too many times and threw the handbrake on. Fix the underlying error (logs), then sudo systemctl restart drongo; two clean cycles and it exits safe mode on its own. |
| Whole board feels sluggish / OOM | Only if you added a local model — it's too big. Drop to a smaller one and add zram (see docs/local-model.md). Cloud-only shouldn't OOM. |
| Whole Pi hard-locks when a project runs (SSH dies, watchdog never fires) | A run ballooned memory and the cgroup thrashed swap instead of OOM-killing. Fixed by MemorySwapMax=0 on both units — deploy it: git pull && sudo ./update.sh. Verify the caps are actually live (ARM boards sometimes boot with the memory controller off): systemctl show drongo -p MemoryMax -p MemorySwapMax must show non-empty values, and cat /sys/fs/cgroup/cgroup.controllers must list memory. If memory is missing, add cgroup_enable=memory cgroup_memory=1 to the kernel cmdline and reboot. |
| Locks up specifically when it scans I2C | Active I2C probing hangs this SoC (PMIC on the bus). Purge the bad method: Brain → Notes & lessons → delete the i2c-scan note/lesson. The shell now denylists i2cdetect/i2cset/i2cget and the build rules forbid probe loops; for the bot's own Python smbus code, hide the dangerous bus from drongo (find it with ls -l /dev/i2c-* + i2cdetect -l on your own login, then a udev rule). |
/sbin/init (PID 1) constantly at 10–20% CPU |
The apport crash-reporter is stuck in a restart-loop (Ubuntu LP#1895286) — not the agent. Fix once: sudo rm -f /var/crash/*, then sudo systemctl disable --now apport.service whoopsie.service. The installer now does this automatically. |
| No Discord alerts | Check DISCORD_WEBHOOK_URL is set in /etc/drongo/drongo.env and alerts.discord.enabled: true. Test the webhook with curl -d '{"content":"test"}' -H "Content-Type: application/json" <url>. |
| "pip: permission denied" / can't install packages | It needs its writable venv at /var/lib/drongo/runtime/venv. sudo ./update.sh (or a restart) creates it; after that pip install … and python … in its shell use that venv automatically. Native packages (numpy etc.) need ARM wheels or build tools; pure-Python (Flask, etc.) just works. |
| LED never blinks | Wrong chip/line (check gpioinfo), LED wired backwards (try active_high: false), or python-periphery missing. Confirm the drongo user is in the gpio group (id drongo). |
| Cloud provider ignored | Its key is blank/invalid in /etc/drongo/drongo.env, or it's rate-limited (the dashboard's usage table shows cooldowns). Blank keys are skipped on purpose. |
web_search returns "no results" |
Keyless scraping is dead (DuckDuckGo blocks it). Best no-cloud option: self-host SearXNG (open-source metasearch, no API key) and set SEARXNG_URL=http://<host>:8888 in /etc/drongo/drongo.env — see Self-hosted search. Or add a free key: BRAVE_API_KEY (2000/mo), TAVILY_API_KEY, or SERPER_API_KEY. Restart after. |
self_update does nothing |
You installed from a ZIP (no git remote). Self-update needs a real remote — git clone instead. Rollback still works either way. |
| Want to start over | sudo /opt/drongo/uninstall.sh --purge then re-install. |
| It's doing something I don't like | touch /var/lib/drongo/runtime/workspace/STOP to halt it now; investigate; delete the file to resume. |
Useful one-liners
journalctl -u drongo -f # watch the agent think, live
journalctl -u drongo-observer -n 20 # what the Dead-Man's-Switch has done
systemctl list-timers 'drongo*' # confirm observer + updater are scheduled
ls /var/lib/drongo/runtime/workspace/ # everything it has builtCloud-only, there's little to tune — the agent is capped at MemoryMax=1200M
(in drongo.service) so it can never starve the OS, and with no local model the
board sits comfortably. If you add a local model, see the RAM/zram/model-size
tuning in docs/local-model.md.
agent/
__main__.py CLI: run | web | once | discover | doctor | verify | seal
config.py config loading + runtime paths
loop.py the autonomous ideate→act→reflect loop + safe mode
llm.py multi-provider router (cloud-first, local fallback, rate limits)
tools.py shell, files, web, image-gen, sensors, dashboards, alerts,
skills/notes/knowledge (RAG), scoped package requests
safeguard.py ★ tamper-resistant safety core (install root:root 0444)
watchdog.py heartbeats, systemd notify, crash-loop self-defence
memory.py SQLite journal / kv / provider usage / skills / pkg policy
alerts.py multi-channel: Discord / LED (GPIO) / ntfy / command
server.py web dashboard (views + control panel)
system/
observer.py external root "Dead Man's Switch" (liveness, rollback, health)
updater.py privileged root self-updater (pull, verify, re-seal, rollback)
pkg-installer.py scoped root apt-installer (validated names, policy + hard allow-list)
firewall.sh OPTIONAL inbound nftables lockdown (SSH-safe, LAN-only)
retro-toolchain.sh OPTIONAL Z80/Amstrad toolchain (sdcc/z88dk/CPCtelera/pasmo)
image-gen.sh OPTIONAL local image generator (OnnxStream)
*.env.example environment templates for /etc/drongo
systemd/ hardened units + timers (agent, web, observer, updater, pkg)
install.sh interactive installer / hardener (preflight, RAM-aware, self-checking)
uninstall.sh clean removal (keeps data unless --purge)
config.example.yaml
- The brain is the free cloud tiers; if they're all rate-limited at once and you haven't enabled a local fallback, the agent idles and retries (it won't wedge). Add a local model if you want a guaranteed floor.
- It can't train a model on the Pi (4 GB CPU) — the "learning" is retrieval over its knowledge base + a curated dataset you fine-tune off-box. See the Brain tab.
- The
shelldenylist is defence-in-depth, not a perfect jail — the real isolation is the unprivileged user + systemd sandbox. Keepallow_sudo: false. - Free cloud tiers change their limits often; tweak
rpm_limit/daily_limitin the config if you start seeing 429s. - The optional last-resort host reboot in the observer is off by default
(
DRONGO_ALLOW_REBOOT=0). The SoC hardware watchdog already covers true lockups.