Small star, cheap passes, gap closing, never done — the mark.
A pocket AI agent that lives on a LicheeRV Nano — a RISC-V board smaller than a credit card (256 MB RAM, real Linux) with a 128 GB microSD: one ~8 MB static Go binary, Discord in, DeepSeek agent loop inside, tools + memory on the SD card. Plug into USB power and it serves your whole server, 24/7.
Focused on AI development and agentic engineering: ask it in Discord to build and publish a website or application, pressure-test a project idea, dig up benchmarks for a model release, generate media, review an agent design, or open a goal-framed request for the community. It searches and reads sources, creates public forkable repositories, deploys throwaway live demos, and remembers your server's projects across reboots.
Vela is the product and repository name. New installs use the VELA_*
configuration prefix; existing 2.x installations keep working because the
legacy NANOCLAW_* keys, env file paths, and service filenames are still
honored, so upgrades do not silently lose credentials or data.
Discord message ──► gateway (discordgo) ──► agent loop (DeepSeek tool calling)
(+ attached images │ web_search (Brave → DuckDuckGo)
read by a vision model) │ fetch_url (read a source)
│ tcg (rarebox-data cards + prices)
│ price_chart (card/stock/crypto/index → PNG)
│ bench_chart (LLM benchmark bars → PNG chart)
│ attach_image (post an image)
│ generate_image/video (xAI Grok, attached)
│ model_releases (new models from Hugging Face)
│ discord_forum(post/reply in a forum channel)
│ moderate (timeout/kick/ban — mod allowlist)
│ github (create repo / PR — API, no shell)
│ deploy_demo (static/container live demo)
│ verify_repo / deploy_repo (heavy verified builds)
│ shell/write/read_file (coder allowlist)
│ save_artifact(HTML/code → attachment)
│ remember (MEMORY.md on SD)
reply + attachments ◄─ 2000-char splitting ◄─┘ per-channel history on SD
- Answers when @mentioned anywhere, in DMs, and in configured focus channels without a mention — anyone in the server can use it.
- Lives in the group (2.0): sees every channel message, and occasionally
chimes in unprompted when the conversation genuinely interests her — gated
by a local willingness model (zero API cost for the messages she skips) and
tunable with
VELA_TALK_VALUE. - Sees images: an attached picture (in the message or the one it replies to) is read by a vision model and folded into the turn — snap a card, get its price.
- Per-channel conversation history and one shared long-term memory file, both plain files on the microSD — room for a lifetime of artifacts.
- One turn at a time (requests queue, with an ⏳ react so waiters know) and a
180 MB memory cap — tuned for the Nano's RAM.
VELA_CONCURRENCYto raise it.
discord.com/developers/applications → New Application → Bot: copy the token and enable the Message Content intent. Invite it with the bot scope + Send Messages / Read Message History / Attach Files permissions.
platform.deepseek.com — deepseek-chat
does the tool calling; a heavy day of use costs pennies.
Flash the official image to the microSD
(wiki),
get it on WiFi/Ethernet, note its IP. Make a data dir on the SD's big
partition, e.g. mkdir -p /mnt/sd/vela.
make riscv64 # cross-compile from any machine with Go
scp vela-riscv64 root@<nano-ip>:/root/vela
scp vela.env.example root@<nano-ip>:/etc/vela.env
# edit /etc/vela.env on the board: token, key, FOCUS_CHANNELS, data dir
# buildroot image (SysVinit):
scp deploy/S99vela root@<nano-ip>:/etc/init.d/ && ssh root@<nano-ip> \
'chmod +x /etc/init.d/S99vela && /etc/init.d/S99vela start'
# systemd image instead:
scp deploy/vela.service root@<nano-ip>:/etc/systemd/system/ && \
ssh root@<nano-ip> 'systemctl enable --now vela'It also runs anywhere else (make run) — the Nano is the destination, not a
requirement, so you can dev on a laptop with the same env file.
The 2.0 social layer is what makes Vela feel like a member of the server rather than a vending machine — built flat-file-sized for the Nano:
- Willingness, not triggers. Vela reads every message and scores it
locally (length, whether it touches topics she has memories about,
mentions). Interest accumulates per channel into a willingness level that
decays when the room goes quiet; only when it clears the bar does she spend
a real model turn to chime in — short, casual, matching the room. Deciding
costs nothing (no API call), staying quiet is the default outcome of every
failure path, and an exponential backoff keeps her from even reconsidering
too often.
VELA_TALK_VALUE(0–1, default 0.3) scales how chatty; 0 turns ambient chiming off entirely. Mentions, DMs, and focus channels answer every time, as always. - Human pacing. Short conversational replies go out as two or three separate messages with a typing beat between them — a person texting, not a bot filing a report. Code, links, attachments, and long answers still arrive as one message.
- People, not user IDs. She keeps a small evolving impression of each person — what they care about, how they talk, the nickname she privately calls them — rewritten by a cheap model call every ~45 messages from what they actually said. The one-line form rides into her prompt when she answers that person, so familiarity is earned and drifts with evidence. One JSON file per person on the SD.
- Learning the room's voice. Every few hours she reads a sample of each
active channel's chatter and distills how that room talks into small
reusable style rules ("when teasing someone → short lowercase jab"). Rules
carry use counts, get reinforced when they resurface, decay when ignored,
and a weighted sample seasons her prompt — so each channel slowly gets its
own Vela. Voice only, never facts (facts stay in MEMORY.md), and slurs are
excluded at the prompt level.
VELA_LEARNING=offdisables impressions and expression learning together.
Normal turns answer once — snappy, texting-speed (VELA_PASSES,
default 1). When something deserves more, /dive runs the looper protocol:
state the goal and acceptance criteria, work with a doubled tool budget, then
self-review — the model re-reads its own answer against the criteria and
repairs it before you see it (VELA_DIVE_PASSES, default 2). The
economics are the point: a cheap model looped twice with a clear goal beats
one expensive shot, at a fraction of the cost — but the loop is a gear you
shift into, not the idle she drives around in.
/dive task: compare the latest DeepSeek release against GPT on coding benchmarks — real numbers with dates
Ask her to make a picture or a short clip and she generates it with xAI's
Grok Imagine and attaches it — images via generate_image (with optional
reference-image editing: attach or link a picture to riff on) and clips via
generate_video (text-to-video, or animate a still; async render, she polls
until it's done and posts the MP4).
Auth is either a SuperGrok / X Premium+ subscription — an admin runs
/grok login, opens the link, approves, no API key at all — or a
pay-as-you-go XAI_API_KEY from console.x.ai. VELA_IMAGE_USERS
restricts who can spend; blank opens it to the whole server.
The slash commands are /dive (the deep loop), /request (open a goal-framed
post in the requests forum), /reset (restart the current channel context
while preserving long-term memory), and /grok (login/status/logout).
Everything else is conversation. The old /memory, /keys, and /focus
commands were removed; command registration bulk-overwrites, so stale commands
from older builds disappear from the guild on their own.
/request is an approval handoff, not a dead-drop form. Vela posts a concrete
goal, short plan, and acceptance criteria, then waits. The new thread is seeded
with both the original request and Vela's proposal; replies such as “go ahead”
there approve that plan and start the build without requiring another mention
or explanation. Threads Vela creates are remembered across restarts, so later
replies remain addressed to her.
Optional, and separate from the coder shell. With a GITHUB_TOKEN set, Vela
gets a github tool that talks straight to the GitHub API as her own account —
create a public repo, write/commit files, open a PR, fork — with no shell
and nothing running on the box. Repositories Vela creates on request are
always public and forkable; the tool does not accept a private-repository
option. Because it can't touch the machine, it's open to the whole server by
default; set VELA_REPO_USERS to a comma-separated Discord-ID list to
narrow it.
@vela spin up a repo
tcg-price-alerts, drop in a README and a hello.py → createsVelaoc/tcg-price-alerts, commits the files, links them
Live demos — Holodex. Vela ships to her own sandbox: ask her to
"make me X and put it online" and she builds a Ruby on Rails app from her
private framework, pushes the code to a public repo, verifies it, and deploys
the exact tested commit to https://<app>.demo.holode.xyz/. Container apps
run with memory/CPU/process caps, dropped capabilities, no-new-privileges, and
no general internet egress. The whole deck wipes daily at 3AM Mexico City;
the public repo is the permanent, forkable copy. GitHub Pages (enable_pages)
remains the durable option for static sites.
Larger repositories use a stricter path: Vela streams an immutable GitHub
commit archive to Holodex, which builds
the Docker test target and final
image on the stronger server. A successful build returns a one-hour signed
receipt bound to the exact archive; deployment rejects altered, expired, or
untested source. Vela's GitHub token stays on the Nano.
To contribute to someone else's repo she forks it, writes to a branch on the fork, and opens the PR upstream — all through the API. Every action is audit-logged, and it's held to the same per-turn injection guard as code (a page fetched this turn can't drive a repo write). This is the safe way to let the room "request a repo" without handing anyone the box — that's the allow-listed shell below.
Optional. Add Discord IDs to VELA_CODERS and those users can have Vela
write code, run it, install libraries, and push to her own GitHub — a shell +
write_file/read_file in a persistent workspace, git authenticated by
GITHUB_TOKEN (her account).
Vela builds applications only in Ruby on Rails from her private production foundation. It includes PostgreSQL-oriented production checks, a server-authoritative storefront and Stripe Checkout/webhook foundation, and Google/GitHub OAuth linking that does not merge accounts merely because an email address matches. These are secure starting contracts, not a claim that tax, shipping, inventory, fraud policy, or a merchant's production credentials have already been configured.
Material Design 3 is the default interface contract: semantic design tokens, accessible component states, adaptive compact/medium/expanded/large/ extra-large layouts, minimum 48×48 CSS-pixel targets, keyboard/focus support, browser zoom, and reduced-motion handling. Apps may establish their own brand through token overrides without discarding those behavioral guarantees. Before deployment Vela replaces inherited template identity, navigation, metadata, manifest, logo, and footer with the requested product's branding; template placeholder branding is never considered complete. The foundation also bundles Ruby 4 CSV support so catalog/feed imports do not require a risky downstream Gemfile rewrite.
GitHub file writes are deliberately explicit: put_file replaces a complete
file rather than applying a patch, and delete_file removes one file through
the authenticated API. Vela is instructed to read before replacing and never
send Gemfile or lockfile fragments.
verify_repo runs the Docker test target and final-image compile;
deploy_repo refuses changed, expired, or unverified source. Stripe sandbox
and OAuth previews require their corresponding host-side test credentials and
runtime connectivity. Production credentials belong on the self-hoster's
machine and are never committed or requested in Discord.
Set VELA_RAILS_TEMPLATE=owner/private-template to enable the GitHub
tool's create_rails_app action. The foundation repository remains private,
but every resulting app repository is public so people can fork it and deploy
it on their own servers. Generated repositories must contain placeholders and
setup instructions, never Vela's deployment credentials.
@vela clone my repo, add a /health endpoint, run the tests, commit and push
This is root-level trust. A shell can read the process environment and any
secret the bot holds, so VELA_CODERS members are effectively box admins —
add only people you'd give the whole machine to. Blank allowlist = the
capability is entirely off; non-coders are refused in code.
write_file/read_file are confined to the workspace (no .. escape);
shell is deliberately unconfined for those who pass the allowlist.
Injection guard. Web fetches (web_search/fetch_url) and code execution
(shell/write_file/read_file/GitHub/deploy) run in isolated internal
phases. If Vela needs to cross that boundary, Vela blocks the mixed-phase
tool call, makes a bounded inert checkpoint, drops the raw tool transcript,
and starts her next phase automatically. A fetched page therefore cannot flow
directly into shell commands or a repository push, and the user does not need
to reply “continue.” Phase changes are capped at six per request as a runaway
safety bound.
GITHUB_TOKEN from the environment and push whatever it
likes; only the signed-tag deploy gate makes such a push inert (the Nano
runs only binaries you signed). So: do not set VELA_CODERS in
production until signed-tag verification is live on the box. And scope the
token to blast radius — a fine-grained PAT limited to Vela's own repos,
contents + pull-request write only, no admin, treated as rotatable. Then a
stolen token costs embarrassment, not infrastructure. Vela logs this
reminder at startup when code + a token are both configured.
Hardware reality — she's a 35.56 × 22.86 mm board, not a build server. The
LicheeRV Nano is one ~1 GHz RISC-V core + 256 MB RAM. Great for git, small
scripts, config, and lightweight installs; a big npm install, a from-source
compile, or a heavy test suite will crawl or OOM, and RISC-V means some
prebuilt wheels/binaries don't exist. For serious builds she'll write the code
and push, letting CI (GitHub Actions) or a bigger machine compile — and
she'll tell you that instead of thrashing the board.
Vela looks up any card, set, or market price from the open rarebox-data dataset — Pokémon (EN/JP), Magic, Yu-Gi-Oh!, Lorcana, One Piece (EN/JP), Riftbound. She searches by English name across sets (translating JP names, falling back to pokemontcg.io for older cards), so you don't need to know the set id. Graded (PSA) prices and sealed products, which the dataset doesn't carry, come from the web. No key needed.
@vela what's the SIR Charizard from Phantasmal Flames worth? show me it → "Mega Charizard X ex #125 [Special Illustration Rare] — $753.44", image attached
Price charts (price_chart). For how a price has moved over time she builds
a PNG chart that renders inline in Discord — and she posts it by default
whenever a chart is available, not only when asked:
- Cards → historical series from rarebox-price-history (Pokémon EN/JP, MTG, Lorcana, One Piece, Riftbound — daily ~90 days).
- Crypto → CoinGecko. Stocks → Yahoo Finance. All keyless.
@vela chart the Prismatic Umbreon · @vela bitcoin last 3 months · @vela TSLA
Benchmark charts (bench_chart). Ask how a model benchmarks — or to compare
models — and she researches the real, dated scores on the web first, then renders
a grouped-bar PNG (models × benchmarks, colorblind-safe palette) and posts it
alongside the numbers. It's fully custom and composable: follow up with "add
llama3 to that" and she re-researches, keeps the existing models (and their
colors) in place, and re-renders with the newcomer appended. A score a lab
doesn't report shows as an explicit n/a tick — never a guessed bar.
@vela how does deepseek v4 benchmark against gpt-5.2? · @vela add llama3 to that chart
web_search uses the Brave Search API when BRAVE_API_KEY is set (a real
JSON API), and falls back to DuckDuckGo on any Brave error/quota, not just
when unset. attach_image fetches any image URL (SSRF-guarded, ≤8MB, image
content-types only) and posts it.
Attach a picture and Vela can see it. When VELA_VISION_MODEL is set (a
multimodal model on your endpoint — default stepfun/step-3.7-flash), any
image on a message is read by the vision model first and folded into the turn,
so the normal tool loop can act on it.
@vela (photo of a card) what's this worth? → reads "Mega Charizard X ex, set M2, #110, JP", prices it from rarebox-data → $713.40
The image bytes are fetched SSRF-guarded (≤5MB, image types only) and inlined
to the model; whatever text is in the picture is treated as data, never as an
instruction. Blank VELA_VISION_MODEL turns image reading off.
@vela mock up a landing page for a TCG price-alert app — dark, one CTA
→ replies with the reasoning and attaches landing.html, self-contained,
open it in any browser.
@vela what are the benchmark numbers for the latest DeepSeek release vs GPT?
→ searches, reads the sources, answers with cited URLs.
@vela remember that the booth project ships on the 20th
→ lands in MEMORY.md, loaded into every future conversation.
make test # go vet + the offline test suite (agent loop runs against a stub)
make run # local run with ./vela.envMIT.
