A private, on-device, voice-first civic concierge for London. Speak a question - "where's the nearest accessible public toilet to Triton Square?", "where do I vote and can I get there step-free?" - and Codeborough finds the right civic service near you, tells you how to get there on monitored, well-served streets, and remembers your situation across the whole conversation. The reasoning, civic data, memory, and safety scoring run on the edge - who is asking and what they asked stays on the box. Voice uses ElevenLabs (the bounty), and two optional, off-by-default network helpers (walking-route geometry, assigned-polling lookup) can each be disabled for a fully air-gapped run - see Privacy.
Built for NVIDIA Hack for Impact - London (Public Services track), targeting the Nemotron and ElevenLabs bounties as well.
Current direction is v3. See
docs/build-plan.mdfor the full architecture, task split, and demo script. This README is the front door; the build plan is the source of truth.
Commercial maps (Google, Apple) already do shops, restaurants and big POIs well - libraries included - so this isn't about finding a café. The gap is the civic layer that councils publish but commercial maps don't ingest: where you vote, winter grit bins, emergency rest centres, council safety cameras, and the civic detail on public toilets (accessible? baby-change? council hours). That data is open but scattered across 33 separate council portals and hard to query in plain language - hardest for the people who most need it (new arrivals, the elderly, the visually impaired, the privacy-conscious).
A voice agent that we build on OpenClaw, grounded in real City of London open data, running entirely on an NVIDIA DGX Spark / ZGX Nano (GB10):
- Voice in/out via ElevenLabs (Talk mode: Scribe STT + Eleven v3 TTS).
- Brain: NVIDIA Nemotron 3 (Nano-30B-A3B, NVFP4) served locally via vLLM, doing tool-calling. Grounding is exact GeoJSON lookups via civic-geo, so this build needs no retrieval/RAG; a Nemotron retriever + content-safety guard are a possible future add-on, not shipped here.
- Grounding: our own
plugins/civic-geo/OpenClaw tool plugin queries the London GeoJSON datasets locally (geocodeincl. offline postcode lookup,find_nearest,get_details,safety_count,route_safety= monitored-streets coverage along the walk,list_coverage). - Memory: one long-lived OpenClaw session + persistent memory, so it recalls earlier turns (the ElevenLabs ≥ 1 h 11 m context-retention bounty).
- Surface: an accessible, answer-first web UI (
ui/) - big mic, one-tap civic buttons, GPS "use my location", spoken answers, 13 languages, Big Simple mode and a clean map. A zero-dep Node bridge (ui/bridge.mjs) sits in front of the gateway and keeps the ElevenLabs key server-side. See the UI README. - Deployment: a one-command containerized stack (
deploy/+docker-compose.yml) for the DGX Spark, with a Docker-enforced privacy boundary - the reasoning core has no internet route; only the voice bridge egresses. See Deployment.
Why on-device matters: privacy (location/queries stay on the box), richer answers (local data surfaces detail a map app can't), and anywhere (self-contained, no cloud dependency).
We're precise about this so the claim survives scrutiny. On the box, always: Nemotron reasoning, all civic-data lookups, postcode geocoding, route-safety scoring, and conversation memory. Who is asking and what they asked never leaves the device.
Three things can use the network, each optional and clearly labelled:
| Network call | Why | How to go fully offline |
|---|---|---|
| ElevenLabs voice in/out | Required by the bounty; best-in-class TTS | Set LOCAL_STT_CMD for on-device whisper STT; mute TTS |
| Walking-route geometry (OSRM) | Turn-by-turn steps | ROUTING_DISABLE=1 → straight-line corridor; safety scoring is local either way |
| Assigned polling station (gov API) | The correct "where do I vote", not just nearest | Unset POLLING_LOOKUP_URL → on-device nearest station (honestly labelled) |
With those three disabled the system is air-gapped end to end. None of them ever sends the user's identity - OSRM sees two coordinates, the polling API sees a postcode, ElevenLabs sees text/audio.
- Find the civic thing maps miss - the nearest polling station, rest centre, library or accessible public toilet, and how to get to it. (polling stations, reception centres, libraries, schools, public toilets) - we return the nearest facility (not your assigned station/catchment yet; that's a postcode-lookup next step).
- Get me there safely - prefers monitored, busy, well-served streets. (CCTV, grit bins) Honest framing: CCTV here is mostly traffic/town cameras = busy roads, not crime surveillance.
- Tell me about it - hours, accessibility, what's there. (all datasets)
How the components come together - voice via ElevenLabs, brain via Nemotron, grounded by our
civic-geo plugin over local data, all orchestrated by OpenClaw on the device.
📐 Full-resolution version → docs/architecture.html - the four
Docker containers on one box: which container holds what, on which network, and what connects to
what (with ports). The reasoning core (vllm + gateway) sits on an internal network with no
route to the internet; the only door out is the default-deny egress-proxy (allows only
*.elevenlabs.io, everything else 403). (GitHub can't render
HTML in a README, so the image above is a static preview of it; or view the HTML live via
htmlpreview
when the repo is public.)
Flow. Every turn runs on a spine that never leaves the box: ① the browser hits only the
bridge on :8091 (the sole inbound port) → ③ the bridge relays to the gateway over an
authenticated WebSocket → ④ the gateway calls vllm on a local, on-box OpenAI-style /v1
API, where Nemotron reasons and tool-calls civic-geo over the local GeoJSON; the grounded
answer then returns back through the gateway and bridge. Voice is an optional excursion taken
only on spoken turns: ② before the brain reasons, the bridge sends the spoken question out through
the egress-proxy to ElevenLabs Scribe (speech→text), and ⑤ afterwards sends the answer to
Eleven v3 (text→speech) so you hear it. A typed turn uses only the spine (①③④) and never
touches ElevenLabs or any network. Session memory persists across turns, so it recalls earlier
context (the ≥ 1 h 11 m ElevenLabs bounty). Reasoning, data, memory and safety scoring run
on-device; only the optional voice / route / assigned-polling calls touch the network, and each can
be disabled (see Privacy).
The diagram above is the deployment topology - which container holds what, on which Docker network, what connects to what (with ports), and where the privacy boundary is enforced. The Flow above is the same system told as a single voice turn; in the running system the browser hits the bridge (
ui/bridge.mjs) first, then the gateway - exactly as the diagram shows. GB10 memory gotchas, the OpenClaw integration points to verify on the box, and what we deliberately skipped under 24 h are indeploy/DOCKER.md.
| Event | NVIDIA Hack for Impact - London |
| Theme | Build autonomous systems that think, act, and run anywhere, for positive impact |
| Platform | On-device on NVIDIA DGX Spark / ZGX Nano (GB10 Grace Blackwell), open-source models |
| Stack | OpenClaw · NVIDIA Nemotron 3 · ElevenLabs · (NemoClaw/OpenShell optional) |
| Track | Public Services - improving access to and efficiency of city services |
| Bounties | Best use of Nemotron · ElevenLabs persistent agent (≥ 1 h 11 m + context retention) |
| Team | Codeborough |
docs/build-plan.md ← the plan (architecture, tasks, demo script) - START HERE
plugins/civic-geo/ ← our OpenClaw tool plugin over the datasets (the part we write)
datasets/<facility>/ ← London civic GeoJSON, per borough + a merged all-london file
ui/ ← accessible, answer-first web UI + zero-dep voice/map bridge
deploy/ · docker-compose.yml · Makefile ← one-command containerized stack for the DGX
| Path | What it is |
|---|---|
docs/build-plan.md |
Build plan v3 - architecture, 3-dev task split, demo script, submission checklist |
docs/setup-runbook.md |
Setup runbook - exact on-the-box commands (Nemotron NVFP4 via vLLM, OpenClaw + ElevenLabs voice, plugin install, 71-min session) + current status |
docs/Codeborough-Pitch-Deck.pptx |
Pitch deck (PowerPoint - GitHub won't preview it; download to view) |
docs/pitch.md · docs/demo-script.md |
Written pitch + 3-min demo script |
docs/submission-checklist.md |
Per-bounty submission checklist (Public Services · Nemotron · ElevenLabs) |
ui/ |
Accessible, answer-first web UI (index.html) + zero-dep voice/map bridge (bridge.mjs) - see its README |
deploy/ · docker-compose.yml · Makefile · .env.example |
Containerized stack for the DGX Spark + the provable privacy boundary - full runbook in deploy/DOCKER.md |
plugins/civic-geo/ |
OpenClaw tool plugin: on-device GeoJSON lookups (see its README) |
datasets/libraries/ |
Libraries (42 across 4 boroughs) |
datasets/reception-centres/ |
Reception / rest centres (47 across 2 boroughs) |
datasets/cctv/ |
CCTV cameras (549 across 2 boroughs) |
datasets/schools/ |
Schools (1000 across 4 boroughs) |
datasets/public-toilets/ |
Public toilets (216 across 5 boroughs) |
datasets/polling-stations/ |
Polling stations (464 across 7 boroughs) |
datasets/grit-bins/ |
Grit bins (376 across 4 boroughs) |
datasets/SOURCES.md |
Coverage matrix, sources, licences, caveats, refresh URLs |
docs/london-structure.md |
How London is organised and why facility data is split across sources |
docs/data-scope-notes.md |
Historical pre-hackathon scope note (now settled - see the build plan) |
LICENSE |
Project licence |
Data covers 8 of 33 London authorities that publish these facilities as open location data;
coverage per facility is partial. The demo focuses on Lambeth (the one borough with CCTV and
all destination types). See datasets/SOURCES.md for the full matrix and caveats.
node plugins/civic-geo/scripts/smoke.mjsThe whole system ships as a single docker compose stack, orchestrated by a Makefile so you
don't type the long commands by hand. vllm (Nemotron NVFP4 brain, GPU), gateway (OpenClaw +
civic-geo + memory), bridge (the UI's voice/map server) and egress-proxy (the one allowlisted
crossing point) come up together. You need Docker with NVIDIA GPU support on the GB10 box and an
ElevenLabs API key - nothing else.
cd ~/Desktop/Codeborough
git pull
cp .env.example .env && $EDITOR .env # set ELEVENLABS_API_KEY (the only required secret)
# Free the unified memory pool first (GB10 shares one 128GB pool between CPU and GPU):
sudo sh -c 'sync; echo 3 > /proc/sys/vm/drop_caches'
free -h # want the `available` column comfortably above ~45 GiB
make gate-test # 1) confirm the Nemotron NVFP4 brain serves on the GB10 (downloads ~16GB once)
make demo # 2) stage weights → docker compose up --build --wait → pre-warm the brain
make prove-boundary # 3) judge-facing proof: core has NO internet, only ElevenLabs egresses
make logs # 4) tail the stack (watch egress-proxy for the only outbound traffic)The bridge serves both the web UI and the API on :8091 (one origin), so there's nothing else to start.
- On the box / same LAN: open
http://<box>:8091in a browser. - From another device over SSH (no LAN access, or to keep it private): forward the one port and open localhost:
# on your laptop: ssh -N -L 8091:127.0.0.1:8091 <user>@<box> # leave running (no output = working) # then in the laptop browser: # http://localhost:8091
The page calls the bridge at its own origin by default, so it works as-is in both cases - no config. Voice (ElevenLabs) needs the laptop to have internet; the civic data, brain, map and memory are all on the box.
What make demo actually runs is plain docker compose up -d --build --wait (preceded by a
weights-staging step and followed by a warm-up call) - so if you prefer raw Compose:
make pull-model # pre-stage the NVFP4 weights into the cb_models volume
docker compose up -d --build --wait
docker compose ps # all services healthy?
docker compose logs -f egress-proxy
docker compose down # stop (keep volumes) · `make nuke` / `down -v` to drop weights tooQuick text sanity check without the UI:
docker compose exec gateway openclaw agent --agent main --message "nearest library to 1 Triton Square"Full runbook - topology diagram, the GB10 memory gotchas, the two OpenClaw integration points to
verify on the box, and what we deliberately skipped under 24 h - is in
deploy/DOCKER.md (quick version in deploy/README.md).
For the non-containerized / bare-box path (running OpenClaw + vLLM directly), see
docs/setup-runbook.md and plugins/civic-geo/README.md.
Codeborough
