development - fswolf/luna-local-agent GitHub Wiki
Project Structure
ai-voice/
│
├── installer/ # install.sh, install.ps1, install.py, uninstall.py, requirements.txt
├── launch.py # what start.sh / luna / luna.cmd run: voice server, then Luna
├── ai-voice-ctl.py
├── alarm.py
├── assistant.py
├── chatroom.py
├── config.json
├── config/ # every setting, one namespace (config.X); one file per area:
│ ├── __init__.py # the namespace itself, plus /set (current, save_setting) and plugin_settings
│ ├── base.py # where things are read from, and her persona
│ ├── server.py # the model server, embeddings, reranking
│ ├── voice.py, desk.py, listening.py, abilities.py, moods.py,
│ └── permissions.py, chat.py, mind.py, portrait.py, actions.py
├── control.py
├── desktop.py
├── diagnose.py
├── factstore.py
├── history.py
├── kokoro-say.py
├── llm/ # the conversation engine, one job per file:
│ ├── prompts.py # the system prompt and generation parameters
│ ├── replay.py # stored history turned back into messages
│ ├── streaming.py # one completion from the model server, streamed
│ ├── rounds.py # the tool loop
│ ├── second_look.py# adaptive thinking, rethink, vote, calibration, grounding
│ ├── turns.py # ask(): one turn end to end; prompt_budget() for /context
│ └── session.py # session switches (are tools working?)
├── lmstudio.py
├── logbook.py
├── longterm.py
├── machine.py
├── mcpclient.py # MCP servers' tools as hers - config.json "mcp", /mcp
├── main.py
├── mood.py
├── notebook.py # her lessons and conversation notes (agent/notebook.db)
├── reflect.py # what she does while idle - session notes, the dream pass, fact cleanup, the greeting
├── skills.py # her recipes for multi-step jobs (agent/skills.db)
├── projects.py # ongoing projects: goal, log of tried/found, next step (agent/projects.db)
├── grounding.py # the grounding check: specifics in a reply that nothing she was given backs up
├── situation.py # the live lines at the top of each turn: window, music, load, plugins, next reminder
├── llama/ # llama.cpp's own server - install.sh, start.sh, server.env
├── embedmem.py # recalling facts by meaning (and reranking), when those servers are up
├── livefeed.py # the live monitor - /monitor, 127.0.0.1:8792 (and /portrait)
├── portrait/ # her animated portrait
│ ├── viewer/ # the page: index.html, boot.js, portrait.js (VRM), live2d.js, gestures.js,
│ │ # look.js (where she looks), emotes.js (hearts, sweat drop...),
│ │ # hologram.js / hologram3d.js, view.py (./viewer.sh),
│ │ # vendor/ (bundled three.js and pixi.js; build.sh rebuilds them)
│ ├── gestures/ # one file per gesture (wave.js, hug.js...), _pose.js helpers
│ ├── models/ # Live2D folders and .vrm files
│ ├── animations/ # .vrma clips (clips.json)
│ └── add_live2d.py
├── introspection/ # injected-thought experiments - make_vectors, trial, report, blind
├── memory-manager/
│ ├── manager.py
│ └── start.sh
├── thoughtlog.py # her reasoning, per turn (agent/thoughts.db)
├── thought-viewer/
│ ├── viewer.py
│ └── start.sh
├── plugins/
│ ├── __init__.py # the loader
│ ├── example.py # template - copy this
│ ├── pomf.py # pomf.tv stream chat
│ ├── irc.py # an IRC channel
│ ├── twitch.py # Twitch chat (reads anonymously; posts with a bot account)
│ ├── youtube.py # YouTube live chat (read-only, API key)
│ ├── cron.py # scheduled jobs - /cron, agent/cron.json
│ ├── minecraft.py # she joins your world and plays towards a goal
│ ├── home.py # your smart home, through a local Home Assistant
│ ├── zork.py # she plays Zork I - /zork, saves in agent/zork/
│ ├── vibecity.py # she plays mayor in an open Vibe City window (its AI Player server)
│ └── discord.py # remote access for the owner over Discord
├── ptt.py
├── reminders.py
├── speech.py
├── interrupts.py # stopping her: each turn's own Stop (speaking, generating, listening); HOME, barge-in, !stop
├── turnctx.py # the running turn: who started it, its passes (job, Discord), what it has seen
├── tests/ # python -m unittest discover tests - turn safety, stopping her
├── timeutil.py
├── tools/
│ ├── __init__.py # the registry - @tool, specs, the on/off switches
│ ├── time.py # one module per group, matching the tools pane
│ ├── reminders.py
│ ├── memory.py
│ ├── files.py
│ ├── desktop.py
│ ├── web.py
│ ├── shell.py # run_command - every command asks
│ └── introspect.py # the Self group - introspect, self_status, recall_episodes, note_lesson, reflect_now
├── transcript.py
├── ui/ # the TUI (prompt_toolkit), one job per file:
│ ├── shared.py # the conversation, the status fields, where the view is
│ ├── messages.py # what the rest of Luna calls: add_message, set_status...
│ ├── text.py # widths, wrapping, frames
│ ├── conversation_pane.py # the conversation pane and the status bar
│ ├── help.py # the help pane
│ ├── tools_pane.py # the tools pane
│ ├── approval.py # the permission popup (ask_approval)
│ └── app.py # style, keys, run(), stop()
├── vision.py
├── voice_loop_kokoro.py
├── wakeword.py
├── webpage.py
├── websearch.py
│
├── agent/
│ ├── agent.json
│ ├── mood.json # how she's feeling, so a restart doesn't wipe it
│ ├── facts.db # the sqlite memory backend (gitignored)
│ ├── thoughts.db # the reasoning log (gitignored)
│ ├── notebook.db # lessons and conversation notes (gitignored)
│ ├── embeddings.db # cached embeddings (gitignored)
│ ├── cron.json # scheduled jobs (gitignored)
│ ├── minecraft.json # places she pinned, per world (gitignored)
│ └── memory.json
│
├── assets/
│ ├── alarm.wav
│ ├── live-view.png
│ ├── luna-desktop.png
│ ├── memory-manager.png
│ ├── thought-viewer.png
│ ├── ui-conversation.png
│ ├── ui-help.png
│ └── ui-tools.png
│
├── history/
│ ├── conversation.json
│ └── transcript.jsonl
│
├── input/
│ ├── __init__.py
│ ├── linux_keyboard.py
│ ├── mac_keyboard.py
│ └── windows_keyboard.py
│
├── reminders/
│ └── reminders.json
│
├── games/zork/ # Zork I (MIT): zork1.z3 and zork_server.py, an MCP server around dfrotz
├── tts/ # experimental speech servers, each self-contained
│ ├── chatterbox/
│ └── qwen3/
│
├── docs/ # these pages - also published to the wiki by .github/workflows/publish-wiki.yml
└── README.md # the front page: what she is, quick start, links here
Tests
python -m unittest discover tests # from the repo folder; no model, UI or network needed
tests/test_turn_safety.py covers the rules that hang off a turn
(turnctx.py): a turn's passes end with it and never reach another
thread; outside content marks only its own turn; web requests ask once
files and outside content mix; a job writes only in job_write_dirs;
allow-all and Discord auto-approve step aside for outside content; the
deny list; a narrow turn (stream chat) can't attach tools outside its
list, and routing state never outlives its turn. One test runs a whole
stream-chat turn through llm.ask against a fake model server.
tests/test_interrupts.py covers stopping her (interrupts.py): a HOME
pressed while a turn waits for the last one still lands; a stop never
reaches the next turn and nothing clears anyone else's; HOME stops your
own turn entirely and only silences the rest; a stop wakes a waiting
loop at once; a stopped turn makes no new request to the model.
tests/test_shared_state.py races real threads at the state several of
them share: walking away (two appraisals finishing together walk her out
once), the on-screen conversation (painting while messages come and go),
and the microphone count PortAudio restarts check. It also checks the ui
package's public names.
tests/test_config.py checks the config package: every live setting has
its constant, BASE_DIR is the repo, and /set and plugin switches change
the value everyone reads (against a temporary config.json).
tests/offline.py keeps every test away from real servers; import it
first in a new test file.
Run them after touching tools, the turn code, the permission checks or anything that stops her.
Extras
python3 kokoro-say.py # read the clipboard aloud through
# the TTS server - bind it to a key