Use nanoMuse for a real task, report what broke, then pick something focused. Issues and pull requests are welcome; for anything larger than a fix, open an issue first so we can agree on the shape. docs/roadmap.md says where help is wanted and where to start in each area. For a quick question or to talk through an idea before writing it up, there is a Discord; decisions still land in issues and pull requests, where they can be found later.
AGENTS.md is the short version of this file for coding agents and for people in a hurry: the layout, the check commands per area, and the conventions below in one screen. The two must agree; when they do not, fix both.
android/— the phone apps. A modified copy of OpenMinis 1.13 imported withgit subtree(GPL-3.0): the Android app underandroid/src/android, the iOS app underandroid/src/ios. Upstream's rules apply here (below), so that the subtree stays pullable.- Everything else is ours — the Python runtime (
nanomuse/), the relay (cloud/), the web console (web/), the desktop app (harness/dsh-nanomuse/and the Electron shell inharness/desktop/), the showcase (demo/), the docs (docs/, served bywebsite/) and the scripts. One set of conventions, the ones in this file; AGENTS.md has the check command for each area and docs/architecture.md explains how the pieces fit. docs/roadmap.md says which version brought what and where help is wanted.
nanoMuse is GPL-3.0-or-later (LICENSE, NOTICE). By contributing you agree that your contribution is licensed the same way. Every commit must carry a Developer Certificate of Origin sign-off — git commit -s adds the line:
Signed-off-by: Your Name <you@example.com>
CI checks it. Not accepted, ever:
- code under a licence that cannot be combined with GPL-3.0 (GPL-2.0-only, SSPL, BUSL, "source-available", proprietary SDKs);
- anything obtained by decompiling, unpacking or scraping the Meta Muse app, or the OpenMinis binaries beyond what their source already shows;
- the OpenMinis or Meta Muse names and logos as part of nanoMuse's own identity (attribution in the About screen and NOTICE is required and stays).
Upstream is a mirror of a private tree, squashed roughly monthly, and does not take pull requests. We have to be able to git subtree pull each release, so:
- Do not rename the Kotlin package
com.openminis.appor the Gradlenamespace. Only theapplicationId(io.github.nanomuse.app) is ours. - Do not rename sandbox paths or CLI names inside the root file system (
/var/minis,minis-global,minis-open,minis-mcp-cli,android-*). They are upstream's contract with itself. - New code goes in new files — package
io.github.nanomuse.*or a new file next to the upstream one. When an upstream file must change, add a// nanoMuse:comment at the spot, and make one change per spot. - Rebranding is a script, not hand edits.
scripts/rebrand.py(names, ids, colours, links — Android and iOS),scripts/gen-android-icons.pyandscripts/gen-ios-icons.py(icons) are idempotent; run them after every upstream pull. Do not fix a rebranding miss by hand — fix the script. - Binary resources (icon PNGs) are overwritten under the upstream name; on a pull conflict take ours (
git checkout --ours). - iOS follows the same rules. The iOS half of upstream lives at
android/src/ios(see docs/ios.md); our Swift goes inandroid/src/ios/NanoMuse/, an edit inside an upstream Swift file carries a// nanoMuse:comment.
Build steps are in android/BUILDING.md (upstream) and, for the toolchain this repository is built with, in scripts/android/ — JDK 21, SDK CMake 3.22.1, NDK r27c, Go 1.25+, gomobile; deps/build_proot.sh builds proot from the android/deps/proot submodule (our fork; portable awk, no gawk needed) and must run before scripts/prepare_android_sandbox.sh.
git fetch openminis --tags
git subtree pull --prefix=android openminis 1.14 -m "Merge OpenMinis 1.14"
git checkout --ours -- 'android/src/android/app/src/main/res/mipmap-*' \
'android/src/ios/Assets.xcassets/AppIcon.appiconset' 'android/src/ios/Resources/AlternateIcons'
python scripts/rebrand.py && python scripts/gen-android-icons.py && python scripts/gen-ios-icons.py
# resolve the remaining conflicts at the `// nanoMuse:` marks, build, run the smoke listgit clone https://github.com/nano-muse/nanoMuse.git && cd nanoMuse
uv venv && source .venv/bin/activate
uv pip install -e ".[dev]" # add ",browser" for the Playwright tool
nanomuse config init # config/config.toml is git-ignoredBefore you push:
ruff check nanomuse tests scripts && ruff format nanomuse tests scripts
mypy # types; config in pyproject.toml
python -m pytest -q # MockLLM only, no network
cd web && npm run check && npm run build # if you touched web/; commit the build
cd cloud && pip install -e ".[dev]" && ruff check . && pytest # if you touched the relayThe desktop app, the Electron shell and the docs site have their own lines in AGENTS.md. Guidelines that apply everywhere: everything that acts goes through the Sentinel with an honest risk; secrets never reach the model ({{vault:NAME}}); test with MockLLM; no internal endpoints or keys in the repo; Ruff, line length 100, type hints; docs are part of the change.
nanoMuse is used in more than one country, so a user-visible string is never added in one language. Write it in English first, then in 简体中文, then in every other locale the file already has — res/values-*/nm_strings.xml on Android (keys start with nm_; upstream's strings.xml is not ours to edit), Localizable.xcstrings on iOS, en and zh in harness/dsh-nanomuse/src/client/locales.ts on the desktop, the console and e-mail templates in cloud/. Dates, numbers and currency use the person's locale. Do not assume a +86 phone number, a Chinese app or Beijing time: SMS codes reach mainland-China numbers only, so a sign-in screen says that and offers e-mail. The voice is the same in every language — plain, specific, no marketing words, no exclamation marks — and Meta's own UI text is never copied verbatim.
The docs site (nanomuse.cn/docs, built by website/ from docs/) is in English and 简体中文. The Chinese pages live in docs/zh/ with the same file names as the English ones — docs/zh/android.md is the twin of docs/android.md — and website/.vitepress/config.mts holds the Chinese nav and sidebar. A change to an English page updates its Chinese twin in the same pull request; a new English page gets a Chinese page and a line in both sidebars. cd website && npm run docs:build is the dead-link check for both languages.
Rules for a Chinese page:
- Facts are identical to the English page: version numbers, paths, commands, config keys, URLs, code blocks, table shapes and link targets. Nothing is added, softened or inflated.
- Every heading below the title keeps the English anchor:
## 安装 {#install}. Links between pages (android.md,configuration.md#connectors) then work in both languages, and so do the sidebar's anchors. - Paths: pages link to pages by bare name (
android.md); images and other files underdocs/are one level up (../screenshots/feed.png); the repository is two levels up (../../nanomuse/config.py). - Voice as everywhere else: plain, specific, human; Chinese idiom, never a word-for-word rendering of the English sentence; no exclamation marks, no marketing words. Second person 你, the agent is 它; menu paths as 设置 → 账号 → 设备, button and switch names in 「」.
- Things are called what the apps' Chinese screens call them. The glossary below is derived from
res/values-zh/nm_strings.xml, thezhblock oflocales.ts,Localizable.xcstrings(zh-Hans),web/src/i18n/zh-CN.tsand the relay console; when a screen and the glossary disagree, the screen wins and the glossary is corrected.
| English | 简体中文 | English | 简体中文 |
|---|---|---|---|
| agent | 智能体 | the relay / nanoMuse Cloud | 中继 / nanoMuse Cloud |
| free, open-source, non-profit | 免费、开源、非营利 | allowance, free allowance | 额度,免费额度 |
| sign in / sign out | 登录 / 退出登录 | one-time code | 验证码 |
| your own key | 自己的 key | provider | 服务商 |
| a plan you already pay for | 你已经在付费的套餐 | model group | 模型组 |
| Data controls | 数据控制 | sync conversations across my devices | 在我的设备之间同步对话 |
| the hub | hub(设备互联) | Devices | 设备 |
| remote control | 远程控制 | trusted (ask-free) devices | 免询问的设备 |
| Reach | Reach | Hands / the hands | 「手」/ 手;动手 |
| the hands' model | 手的模型 | the operator | 操作器 |
| computer use | 电脑操作 | the capsule | 胶囊 |
| the stage / the glow / the marker | 舞台 / 光晕 / 标记 | the trajectory | 轨迹 |
| accessibility service / overlay | 无障碍服务 / 悬浮窗权限 | Accessibility / Screen Recording (macOS) | 辅助功能 / 录屏 |
| Sentinel | 哨兵(Sentinel) | approval, approval card | 审批,审批卡片 |
| Allow once / for this session / always / Deny | 允许一次 / 本次对话都允许 / 总是允许 / 拒绝 | destructive / outbound / money / install | 删除 / 发送 / 付款 / 安装 |
| Permissions | 权限 | workspace / shared folders | 工作区 / 共享文件夹 |
| the vault | 保险库 | taint, tainted | 污染,已污染 |
| Feed / Ideas / Goals / Library | 动态 / 点子 / 目标 / 资源库 | Artifacts / Media | 构件 / 影音内容 |
| Routines / goal check | 例程 / 目标检查 | the rail / the rooms | 侧栏 / 房间 |
| main chat | 主要聊天 | side chats | 旁聊 |
| memory / daily log | 记忆 / 日记 | SOUL, IDENTITY.md, MEMORY.md | unchanged |
| avatar / the avatar studio | 形象 / 形象工作室 | moods (idle, working, waiting, happy, error) | 状态(待命、干活中、等你回复、搞定、出了点问题) |
| picture model / video model | 图像模型 / 视频模型 | skills / MCP server | 技能 / MCP 服务器 |
| connectors | 连接器 | coding agents | 编程助手 |
| channels (Feishu, DingTalk, WeCom, Telegram) | 聊天入口 | deliver here | 推送到这里 |
| the web console / the web app | 网页控制台 / 网页版 | the desktop app | 桌面版 |
| the phone app | 手机 App | the runtime / the local runtime | 运行时 / 本地运行时 |
| the showcase | 展示站 | self-hosting | 自己部署 |
| quick chat / dictation | 快速唤起 / 听写 | roadmap / changelog / release notes | 路线图 / 更新日志 / 发布说明 |
- The subject line follows Conventional Commits:
type(scope): what changed, in the imperative, no full stop —feat(android): hands capsule shows the model's thought,fix(relay): keep the face a device drew when another renames,docs(readme): move the translations to docs/readme/,test(gateway): sighted model for the operator lane. Types:feat,fix,docs,style,refactor,perf,test,build,ci,chore,revert; the scope is the part of the tree (runtime,android,web,desktop,relay,showcase,mobilegym,harness,docs,deps…). The body says why, in plain words. The Commits check on a pull request fails on a subject that does not fit; merge commits are exempt. - One pull request, one topic. Screenshots for anything visible in the app.
- Say which device and Android version you tested on. Phone-side features are tested on real hardware; the emulator is x86_64 and cannot run the arm64 APK.
mainis protected. Code lands through a pull request, and the merge waits for four checks: the Python runtime on Ubuntu / Python 3.12, the web app build, the Android debug APK, and the DCO sign-off. Each workflow first works out which tree the change touched and skips the jobs that do not apply, so a docs-only pull request is not held up by a build it never needed. The rest of the matrix (other Python versions, macOS, Windows) runs and shows up, but does not gate the merge; the Python runtime's Windows job is marked experimental (its shell tool assumes a POSIX shell) and cannot fail the build — the Windows desktop app is built and released by its own workflow and is supported.- Merges are rebase or merge commit, never squash: the
Signed-off-byon each commit is the record, and squashing would drop it. A merge commit is also whatgit subtree pullneeds for theandroid/tree. - Maintainers work the same way: one branch and one pull request per version (
0.1.39,0.1.40, …), merged when green, tagged and released frommain(the recipe is below). Documentation and copy — READMEs,docs/, release notes — may go straight tomain. - Dependabot opens its pull requests once a month, grouped; they are merged when the checks are green, and a bump that changes the committed web bundle gets its rebuild right after.
A version is a plain number — 0.1.39, 0.1.40, … — because the apps' update check compares them; each release also carries a one-word English codename (Foundation, Identity, Home, … Keys, Clear; CHANGELOG.md has the list) and its title is nanoMuse <version> · <Codename>. The apps, the runtime, the web console and the desktop app share one version; the relay has its own (cloud/pyproject.toml) and moves only when it changed. Versions move in the release commit and nowhere else.
- The release commit
build(release): <version> <Codename>, on its own branch and pull request.scripts/release-bump.sh <old> <new> <old-code> <new-code> [<relay-old> <relay-new>]moves every version file (pyproject.toml,nanomuse/__init__.py,CITATION.cff,web/package.json, the twoharness/*/package.jsonand their lock files, the relay console'sVERSION, theVERSION_NAME/VERSION_CODEinscripts/rebrand.py); thenpython scripts/rebrand.pywrites the AndroidversionName/versionCodeand the iOSMARKETING_VERSION(a second run must printclean).scripts/release-docs.py <old> <new> <OldName> <NewName> <date>opens the version's block inCHANGELOG.mdunder Unreleased and moves the download links ofREADME.md,docs/readme/*anddocs/index.mdto the new tag. By hand: the lead paragraph of theCHANGELOG.mdblock and the empty### Cloud / Runtime / Web / Desktop / Android / iOS / Projectheadings for the next Unreleased;docs/releases/v<version>.mdfrom docs/release-notes-template.md — a short story, Highlights, Upgrade Notes, Community, English first and the same in Chinese in a collapsed block, every link checked; the News lines of the ten READMEs (the new release on top, the fourth dropped); the past-releases row indocs/roadmap.md; any "next release" cell indocs/parity.md. Run the checks for what changed (AGENTS.md;scripts/rebrand.py→clean,connectors-json.mjs --check,providers-json.mjs --check,cd website && npm run docs:build), and the iOS · build check workflow on the branch. Merge when green;Mis the merge commit. - The APK and the GitHub release.
scripts/release-apk.sh <version>atMbuilds the release APK, verifies the signature and writes the sha256;scripts/release-apk.sh <version> --publish-only --target Mtagsv<version>onMand creates the release (marked Latest;--prereleasefor one that is not) withdocs/releases/v<version>.mdas the body. What's Changed, New Contributors, Contributors and the Full Changelog link are filled in from the pull requests merged since the previous tag byscripts/release_notes.py(scripts/release_notes.py <version>shows the body it would publish). The tag starts the desktop, harness, Docker and test workflows; the desktop installers, theirSHA256SUMS-desktop.txt, the harness tarball and itsSHA256SUMS-harness.txtare attached to the release by CI as they finish, the Docker image lands onghcr.io/nano-muse/nanomuseas<version>,<major.minor>andlatest. Every workflow must end green before the release is announced. The signing key is one key for every version so an update installs over the previous one; it is not in the repository and not in CI. - The relay, when its version moved:
cloud/deploy/nanomuse-hk/deploy.shfrom the checkout atM;/healthzmust answer with the new version. - TestFlight. The iOS · TestFlight workflow on
main(or a tagios-*) uploads the build; its number is the run number. Once App Store Connect shows it processed, the external group gets it after Apple's beta review;docs/releases/v<version>.mdnames the build number. docs/ios.md has the details. - The docs site and the homepage (nano-muse/nano-muse.github.io, live at nanomuse.cn within a minute of a push to
main):cd website && DOCS_BASE=/docs/ npm run docs:buildand copy.vitepress/dist/to the site'sdocs/; inindex.htmlthe version line (both languages), every download link and thenanomuse.cn/dl/v<version>/mirror link, the Docker tag, and the News list — the last three releases, the new one first, two sentences in each language. The download mirror itself fills in on its own within fifteen minutes of the release.
A release that is only a docs fix goes straight to main; a release note that turned out wrong (a build number, a link) is fixed by a small pull request, never by rewriting history.
Open a private security advisory on GitHub rather than a public issue.