forked niri
TypeScript 97%
Nix 2%
CSS <1%
JavaScript <1%
Dockerfile <1%
Python <1%
Shell <1%
HTML <1%

README.md

Niri #

Run the server #

Nix is the source configuration format. The NixOS module generates JSON directly from services.nano.settings and services.nano.agents; see configuration. For a source checkout:

npm ci
cp config.example.nix config.nix
cp agents/mira.example.nix agents/mira.nix
# edit both Nix files; keep credentials in a separate JSON secrets file
nix eval --json --file config.nix > config.json
nix eval --json --file agents/mira.nix > agents/mira.json
npm start -- --config config.json

The server reads every .json file in agents/. Files ending in .example.json are skipped. Shared server settings control the bind address; command-line --host and --port flags take precedence.

The web dashboard is served by the control plane at /ui/ on the same port. Nix packages include the built UI automatically. With services.nano.agents configured, open http://<control-host>:<services.nano.controlPort>/ui/ (the default control port is 3000). No separate web service is needed. For a source checkout, npm start builds the UI before starting the server; you can also build it explicitly with npm run build:web.

Run a client #

cd apps/client
npm run start

The client listens on 0.0.0.0:3002 and runs shell, read_file, edit_file, and image_tool from the client's home directory.

Discord controls #

The configured Discord owner can use /fast to toggle priority service for Codex models, or /fast enabled:true and /fast enabled:false to set it explicitly. /model info shows the current setting. Changes apply to subsequent Codex requests (including subagents and summaries); off uses the default service tier. The setting is local to the agent runtime and resets on restart to the active model or preset's fast value (false by default).

/interrupt stops the active model generation or foreground blocking job while keeping the session. /discard stops all session work, archives the raw snapshot as memories/discarded-{datetime}.json, and removes it from the active runner. /compact runs an immediate context compaction. /compact force:true requires the dedicated summary model and hands its detailed play-by-play to the primary model to journal on the next turn. Add snapcompact:true to preserve the discarded span as a bitmap frame (with a text fallback if rendering is unavailable). Shell calls still running after two minutes detach automatically; the model can inspect their status and stdout or stop them with job_list, job_status, job_stdout, and job_cancel.

The owner also receives a DM when a terminal model API error occurs, or when a transient error repeats three times. Alerts are deduplicated for six hours and include a Mute this error forever button. Muting is stored in nano.db and applies only to that error fingerprint.

Set frontend.enable = true to run a second virtual nano for every model-eligible Discord event except DMs authored by discord.owner_id. It sees the main soul through a private, three-way-rebased patch and shares memories with the main agent, but has its own session and model loop, no executor or skill access, no owner-DM lookup access, and a durable agent_send / agent_channel bridge to the main agent. Omit frontend.preset to follow the main agent's live model; set it to a named model preset to pin a separate one.

Agent memory #

Memory lives on the server under the agent's home, separate from any attached client:

Path Contents
<home>/soul.md The agent's identity and enduring self-description
<home>/memories/ Long-term Markdown memories, including core notes, journals, and people files
<home>/state/ Session, rest snapshot, and failover state
<home>/nano.db The searchable memory index and other durable runtime data

For example, an agent with home: /home/nano keeps long-term memories in /home/nano/memories.

For routine maintenance, ask the agent to inspect, organize, or repair their own memories using the memory tools. Treat soul.md and memories/ like a private diary: do not read or change them unless the agent asks; it's weird if you do. For backup or recovery, stop the worker and preserve the entire agent home rather than copying only the Markdown files.

Agent JSON specification #

One file in agents/ is one agent. Files ending in .example.json are skipped. The NixOS module generates these files from services.nano.agents. For standalone use, evaluate an agent Nix attribute set to JSON. JSON uses nested objects; a key containing a dot is a literal key, not a nested setting.

An agent file describes only what the control plane owns. Everything the agent runs on — model, fallback, tools, context, Discord, subagents — lives in the runtime JSON named by config, in the same format a standalone nano --config … run reads. There is one config dialect, and both ways of starting a worker feed it the identical file.

Top level #

Field Type Required Default
id string matching [a-zA-Z0-9_-]+ no filename without .json
name string no id
port integer 1..65535 no control port plus the file's sorted position
home path no data/agents/<id>
client local or an HTTP(S) URL yes —
workspace path no repository root; applies to client = "local"
config path, or a list of paths no —
secrets path no —
settings object no {}

Relative home, workspace, config, and secrets paths resolve from the repository root. A config or secrets path that does not exist fails startup.

config and secrets #

The worker is spawned with these as --config and --secrets flags. config may be a list: each file is deep-merged over the previous one, so several agents can share a base file and layer their own differences on top. secrets is merged last, so a key set in both resolves to the secrets value.

config = [ "/etc/nano/config.json" "/etc/nano/agents/mira.json" ];
secrets = "/run/agenix/nanoSecrets"; # JSON contents; the filename may be extensionless

Credentials reach the worker as files, never as environment variables — the agent's own shell tool inherits that environment.

settings #

settings accepts string, number, and boolean values keyed by uppercase runtime setting names, and becomes the worker's environment. It is for the settings that are genuinely environment-shaped; anything the runtime reads from JSON belongs in config. Names the server manages — PORT, HOME, NANO_CONFIG, NANO_SECRETS, and the NANO_* prefixes — are rejected.

TODO: Promote the remaining active settings into first-party configuration sections for tools, context management, Discord batching/cooldowns, state, and bridges. Remove the settings that have no runtime reader.

Tools and model calls #

Setting Staged value Purpose
IMAGE_TOOL_MAX_BYTES 1000000 Maximum image size accepted by the image tool.
PRIMARY_TOOL_CHOICE auto Tool-choice mode for primary model requests: required, auto, or none.
FALLBACK_TOOL_CHOICE auto Tool-choice mode for fallback model requests.
FALLBACK_ENFORCE_CONTEXT_LIMIT false Enforce the configured fallback context limit before sending a request.

Context management #

Setting Staged value Purpose
CONTEXT_COMPACT_TRIGGER_TOKENS 100000 Start context compaction at this token estimate.
TOKEN_NUDGE_THRESHOLD 120000 No current runtime reader.
CONTEXT_COMPACT_TARGET_TOKENS 65000 No current runtime reader.
CONTEXT_COMPACT_RECENT_MESSAGES 80 No current runtime reader.
CONTEXT_COMPACT_CHUNK_MESSAGES 32 No current runtime reader.
CONTEXT_COMPACT_SUMMARY_MAX_CHARS 500000 No current runtime reader.

Discord #

Setting Staged value Purpose
DISCORD_GATEWAY_TRACE true Log gateway tracing details.
DISCORD_GATEWAY_RAW_FALLBACK true Use raw gateway events when the normal event path cannot handle an event.
DISCORD_BATCH_INTERVAL_MS 8000 Interval between Discord batch checks.
DISCORD_BATCH_ONLY_CONFIGURED true Limit batches to DMs and configured scan channels.
DISCORD_PENDING_AUTO_SEEN_MINUTES 10 Mark pending messages seen after this many minutes.
DISCORD_BATCH_SCAN true Scan configured channels before building each batch.
DISCORD_BATCH_MAX_MESSAGES 120 Maximum messages included in one batch.
COOLDOWN_CHANNELS channelId,2000,0400 Channel response windows as repeated channelId,startHHMM,endHHMM triples.
COOLDOWN_TZ America/New_York Time zone used for cooldown windows.
DISCORD_WAKE_ON_DM true No current runtime reader.
DISCORD_WHITELIST Discord user ids No current runtime reader; use discord.dmWhitelist.

State and process #

Setting Staged value Purpose
NANO_MIGRATE_LEGACY_STATE true Copy root-level session snapshots into the agent state directory once.
AGENT_UID 1001 No worker runtime reader; used only by the Docker build and Compose configuration.
AGENT_GID 1001 No worker runtime reader; used only by the Docker build and Compose configuration.

Bridges #

Setting Staged value Purpose
ANTIGRAVITY_BRIDGE_ENABLED Niri: false; Lyra: true Start the Antigravity bridge.
ANTIGRAVITY_BRIDGE_PORT 8000 Antigravity bridge loopback port.
ANTIGRAVITY_BINARY_PATH /home/nano/.local/bin/agy Antigravity executable.
CODEX_BRIDGE_ENABLED Niri: true; Lyra: false Start the Codex bridge.
CODEX_BRIDGE_PORT 8001 Codex bridge loopback port.
CODEX_BRIDGE_MODEL gpt-5.6-sol Model presented by the Codex bridge.
CODEX_BRIDGE_REASONING_EFFORT low Codex bridge reasoning effort.

Agent ids, worker ports, canonical home paths, Discord tokens, and enabled bridge ports must be unique.

Keep credentials in separate JSON secrets files with mode 0600. Tool-client endpoints should stay on loopback or a trusted private network.

Check the repository #

npm run typecheck
npm test
npm run build