Quick start
nix run github:juspay/agent-distro # pick from the list
AI_HARNESS=claude nix run github:juspay/agent-distro # skip the listTo keep the agents installed and updated, see Install and keep updated.
- Systems.
x86_64-linux,aarch64-linux, andaarch64-darwin. - Binary cache. Pass
--accept-flake-configto use the cache this flake names;1 without it, Oh My Pi is built from source.
The chooser
With several profiles, the chooser is master–detail: profiles are tabs in the header, harnesses are the list on the left, and the highlighted harness fills the panel on the right.2 ←/→ (or Tab) switch profile, and nothing in the box is ever cut.
Here vanilla/pi is remembered from last time.3
╭─ agent-distro ───────────────────────── juspay · • vanilla ── 6 harnesses ─╮
│ juspay · Juspay skills + Kolu, via Juspay's LiteLLM gateway │
├──────────────────────────────┬─────────────────────────────────────────────┤
│ ❯ ✓ Oh My Pi 18.7.0 │ Oh My Pi 18.7.0 │
│ ✓ Codex 0.160.1 │ gateway or own provider · extensions │
│ ✓ Claude Code 2.1.292 │ │
│ ✓ OpenCode 1.18.35 │ Signed in │
│ OpenCode v2 2.0.24 │ LITELLM_API_KEY │
│ ✓ Pi 1.0.4 │ anthropic │
│ │ openai │
├──────────────────────────────┴─────────────────────────────────────────────┤
│ / filter… │
╰────────────────────────────────────────────────────────────────────────────╯
↑↓ jk move ←→ profile Enter launch / filter q quit
| Variable4 | Values | Effect |
|---|---|---|
AI_HARNESS |
a directory under harnesses/ |
Launches that harness without the list5 |
AI_PROFILE |
a directory under profiles/ |
Chooses the profile;6 on its own, narrows the list to that profile |
AI_GATEWAY |
0 |
Keeps the plugins but skips gateway initialization |
AGENT_DISTRO_PLUGINS |
:-separated plugin directories |
Loads more plugins for that launch; see Plugins at launch |
By name
| Command | Does |
|---|---|
agent-distro <harness> |
Launches directly |
agent-distro <profile> <harness> |
Selects both |
agent-distro <profile> |
Narrows the chooser |
For example:
agent-distro codex --version
agent-distro juspay claude --version
agent-distro juspay
agent-distro --list # profile harness title version, one row per line
agent-distro --list --json # the same, as JSON (below)- Precedence.
AI_HARNESSandAI_PROFILEtake precedence over positional selections.7 - Passthrough. Recognized leading names are consumed; other arguments and everything after
--are passed to the harness unchanged.
One profile
Narrowed to one profile,8 the header shows that profile’s name instead of tabs, and the row under it is the same. Versions reflect the packages pinned at build time.9
╭─ agent-distro · juspay ────────────────────────────────────── 6 harnesses ─╮
│ juspay · Juspay skills + Kolu, via Juspay's LiteLLM gateway │
├──────────────────────────────┬─────────────────────────────────────────────┤
│ ❯ ✓ Oh My Pi 18.7.0 │ Oh My Pi 18.7.0 │
│ ✓ Codex 0.160.1 │ gateway or own provider · extensions │
│ ✓ Claude Code 2.1.292 │ │
│ ✓ OpenCode 1.18.35 │ Signed in │
│ OpenCode v2 2.0.24 │ LITELLM_API_KEY │
│ ✓ Pi 1.0.4 │ anthropic │
│ │ openai │
├──────────────────────────────┴─────────────────────────────────────────────┤
│ / filter… │
╰────────────────────────────────────────────────────────────────────────────╯
↑↓ jk move Enter launch / filter q quit
Signed-in marks
✓ marks a harness that can start without a login step; the panel beside it says what was found.10 A harness with nothing to start from shows a dim Not signed in and no mark.11
| Harness | Looks at |
|---|---|
| Claude Code | oauthAccount.emailAddress in $CLAUDE_CONFIG_DIR/.claude.json,12 or the name of ANTHROPIC_API_KEY / CLAUDE_CODE_OAUTH_TOKEN |
| Codex | the email claim of the OpenID token in $CODEX_HOME/auth.json,13 or OPENAI_API_KEY |
| Gateway harnesses | the profile’s gateway.keyEnv, then the provider ids it also has credentials for14 |
| Own-provider harnesses | the ids in the harness’s store,15 plus the provider API-key variables it reads from the environment |
AI_GATEWAY=0 puts a gateway harness back on its own provider. This is a launch-time fact: --list and --list --json never carry it.
Keys
| Key | Does |
|---|---|
| ↑/↓, j/k | Move in the list |
| ←/→, Tab/Shift-Tab, h/l | Switch profile (several profiles only) |
| Enter | Launch the highlighted harness |
| / | Filter the list by title or tagline |
| Escape (filtering) | Clear the filter, keeping the cursor on its row |
| Escape, q, Ctrl-C | Quit without choosing |
- Filtering. The filter line shows the query as typed and how many rows match.16
- Footer. Lists only the keys that apply at that moment.
- Colour. Follows
NO_COLORand the terminal type. - Small terminals. Small or unsupported terminals get a numbered list on stderr instead.17 One that shrinks below that once the box is up shows a notice until it grows again.18
- No terminal. Without a terminal on stdin, agent-distro says which variables to set instead of drawing anything.
Remembered choice
An interactive selection is remembered in ${XDG_STATE_HOME:-$HOME/.local/state}/agent-distro/last-choice.19 Next time the default profile still opens, and the remembered profile and harness are marked with •.20
- Updates it. Every interactive choice, including
agent-distro <profile>. - Never updates it. Direct selections:
AI_HARNESS,agent-distro <harness>, oragent-distro <profile> <harness>.
Listing as JSON
agent-distro --list --json prints what the chooser draws, on one line, for programs that offer the same choice (such as kolu).21 Here it is pretty-printed, with each profile cut to one harness:
{
"profiles": [
{
"description": "Juspay skills + Kolu, via Juspay's LiteLLM gateway",
"harnesses": [
{
"name": "claude",
"tagline": "Anthropic login · plugin dirs per session",
"title": "Claude Code",
"version": "2.1.291"
}
],
"name": "juspay"
},
{
"description": "Upstream harnesses with your own provider",
"harnesses": [
{
"name": "claude",
"tagline": "Anthropic login · plugin dirs per session",
"title": "Claude Code",
"version": "2.1.291"
}
],
"name": "vanilla"
}
]
}| Field | Rule |
|---|---|
profiles |
The first profile is the default |
harnesses |
In menu order |
name |
Unique at its level, free of whitespace and /; what AI_PROFILE or AI_HARNESS (or a positional selector) takes |
version |
The display version, as --list prints it22 |
Keys within an object are sorted. --list takes no other arguments.23
Plugins at launch
A profile fixes the plugins its harnesses start with. Put more Agent Plugins directories on AGENT_DISTRO_PLUGINS, and every harness loads them too, in every profile, for that launch:
AGENT_DISTRO_PLUGINS=~/src/my-plugin agent-distro claude
AGENT_DISTRO_PLUGINS=/nix/store/…-kolu/agent-plugin:~/src/my-plugin agent-distro juspay ompUnset or empty, the launchers behave exactly as they do without this feature.
- Entries. Directories separated by
:,24 with empty components ignored.25 An entry that is not a directory fails the launch, naming it. - Read like a profile’s plugins. Each directory goes through the same reader: an invalid manifest fails the launch with a message naming the field. Lesser problems are reported while the launch goes on.26
- Matched by name, never by version. A plugin whose
plugin.jsonnamematches one of the profile’s replaces it; on the variable, the last of a name wins.27 - Translated once. Each harness’s translation of a plugin is cached under
${XDG_CACHE_HOME:-~/.cache}/agent-distro/plugins/<key>/<harness>/.28 An edited checkout is translated again on its next launch.29 - Kept while in use. Each launch marks the translations it uses; a launch that translates something new removes what no launch has used for 14 days.30 With no harness running, removing
~/.cache/agent-distro/pluginsis always safe. - For that launch only. Unset the variable and the harness is back to the profile’s plugins.31 Each harness’s README says how.
Install and keep updated
The Home Manager module puts every harness on your PATH and keeps them on the latest build: it checks four times a day,32 installs only what the binary cache already holds, and never compiles.
With Home Manager
{
imports = [ inputs.agent-distro.homeManagerModules.default ];
services.agent-distro = {
enable = true;
profile = "vanilla"; # default: juspay
};
}Home Manager creates the state directory and schedules the updates for you.33
Without Home Manager
Update by hand:
nix profile install github:juspay/agent-distro#juspay
nix profile upgrade juspayOr keep the cache-only updates, but drive them yourself:
agent-distro.lib.mkUpdater(see Library) gives thecommandand a runnableprogram.34agent-distro.lib.mkShimskeeps installed commands pointing at the updated bundle.- Scheduling is yours. The updater schedules nothing itself, so run it on your own cadence.35
- Setup is yours. Create the
stateDirectoryyourself, and run--cache-warningsonce to surface an unusable cache.36
Progress output
Append --progress to command to drive the same update from a program that wants to show the download. Stdout is then one JSON object per line,37 and every human message moves to stderr. Exit codes and the history log are the same as without it.
The binary cache
The updater never compiles harnesses from source; it passes the project cache to every update.38 On a multi-user install where you are not a trusted Nix user, add the cache to your NixOS or nix-darwin configuration:39
nix.settings.extra-substituters = [ "https://cache.nixos.asia/oss" ];
nix.settings.extra-trusted-public-keys = [ "oss:KO872wNJkCDgmGN3xy9dT89WAhvv13EiKncTtHDItVU=" ];Without it, or when the cache does not yet hold the whole bundle, the update is skipped and your current version keeps working.
history.log records the skip,40 and activation warns while the cache is unusable.
Troubleshooting
| Symptom | Where to look |
|---|---|
| Updates not arriving | systemctl --user status agent-distro-update; run manually with systemctl --user start agent-distro-update |
| What was updated or failed | ~/.local/state/agent-distro/history.log41 |
| Full run output, Linux | journalctl --user -u agent-distro-update |
| Full run output, macOS | ~/.local/state/agent-distro/<source>/update.log |
Build your own distribution
mkdir my-distribution && cd my-distribution
nix flake init -t github:juspay/agent-distroPoint my-skills at your Agent Plugins repository and edit profile.nix:
| Field | Meaning |
|---|---|
name |
Distribution identifier; Codex marketplace is <name>-ai |
description |
Shown with the profile in the picker |
plugins |
List of Agent Plugins directories42 |
gateway |
null, or { url; keyEnv; models = { large; small; }; keyHint; } for a LiteLLM proxy |
packages |
Optional pkgs: [ … ]: commands the plugins’ MCP servers name, put first on PATH for every harness43 |
- Start from. The template includes a gateway example;
agent-distro.profiles.vanillais the reference profile shape. - Pin. Commit
flake.lockto pin your build. - Run. Your team runs
nix run github:<you>/my-distribution. - Update.
nix flake update agent-distro.
agent-distro.lib.mkFlake { profile; systems ? [ "x86_64-linux" "aarch64-linux" "aarch64-darwin" ]; cache ? …; }
# → packages.<system>: every harness, default picker, and <profile.name> bundle
# (a bundle's bin/ carries its harness commands and `agent-distro`, its own picker)
# apps.<system>: every harness and default picker; homeManagerModules.default;
# lib: the same eight-name library attrset this flake exposes
agent-distro.lib.mkLaunchers { pkgs; profile; }
# → every harness launcher, plus picker and bundle (the bundle's bin/ also holds
# that picker as `agent-distro`, defaulting to the profile)- Outputs.
mkFlakereturnspackages,44apps,homeManagerModules.default, and the samelibattrsetflake.nixexposes; add other outputs with//. - One profile. A single-profile distribution draws the narrowed list above: one profile’s rows under its description.
mkFlakegives you the individual launchers and a bundle named after the profile, with the picker as itsdefault. - Cache.
mkFlaketakes an optionalcache = { url; publicKey; }, defaulting to agent-distro’s cache. Push your own builds to a cache and pass it, or your users’ updates are skipped.45
For NixOS, with your distribution bound as distro:
environment.systemPackages = [ distro.packages.${system}.my-profile ];
AI_GATEWAY=0 nix run .#ompskips gateway initialization while keeping plugins.46
Bundle layout
A profile’s bundle (nix build .#<profile.name>) is self-describing: a consumer reads two files rather than running a command for information.
bin/<harness>… # one launcher per harness
bin/agent-distro # the picker, defaulting to this profile
share/agent-distro/profile.json # {"description": "…", "name": "<profile.name>"}
share/agent-distro/versions # <harness>\t<title>\t<version>, one line per harness, in menu order
profile.json comes from the same profile attributes as the picker’s --list --json, so the two cannot drift.47
Library
Beyond the launchers, agent-distro.lib exposes the pieces Home Manager uses under the hood. Any Nix consumer, not only Home Manager, can keep a profile installed, updated, and chosen.48
agent-distro.lib.stateDirectory { xdgStateHome; flake; profile; }
# → the string "<xdgStateHome>/agent-distro/<sha256 of {flake, profile}>", the
# same hash Home Manager computes today. One function, so a consumer and the
# updater can never disagree on where `current` lives.
agent-distro.lib.mkShims { pkgs; bundle; stateDirectory; }
# → a derivation whose bin/ holds one shim per bundle.commands, each
# `exec "<stateDirectory>/current/bin/<name>"` when executable, else
# `exec <bundle>/bin/<name>`; identical text to the Home Manager module's shims.
# `bundle` must be a bundle from `mkLaunchers` of this agent-distro (it carries
# `commands`); anything else is an eval error naming the problem.
agent-distro.lib.mkUpdater { pkgs; bundle; flake; profile; stateDirectory; history; nix; substituters; }
# → { config = <the generated JSON file>; command = [ node update.ts config ];
# program = <a writeShellApplication running `command "$@"`>; }. The Home
# Manager module's systemd ExecStart, launchd ProgramArguments (`++ ["--scheduled"]`)
# and activation `--cache-warnings` all come from this; `command ++ [ "--progress" ]`
# is the same update with JSON progress on stdout. `bundle` must be a
# `mkLaunchers` bundle with a `runtime` (post-#59 ones do); the period/offset
# come from `lib.schedule` and are not overridable here; the updater never
# compiles, handing nix only `substituters`.
agent-distro.lib.mkPicker { pkgs; profiles; default; }
# → lib/picker.nix's derivation: the interactive profile/harness chooser, where
# `profiles = { <name> = { profile; launchers; }; … }` — one entry per profile,
# `launchers` the `mkLaunchers` result for it — and `default` names the preselected one.The updater’s schedule is one lib/schedule.nix, exposed as agent-distro.lib.schedule: updateHoursUTC, defaultFrequency, updatePeriodSeconds and updateOffsetSeconds. The module, mkUpdater and any consumer read the same numbers.
Profiles
| Profile | What it is |
|---|---|
vanilla |
Upstream harnesses with your own provider; no plugins, no gateway |
juspay (default) |
Juspay skills + Kolu, via Juspay’s LiteLLM gateway |
Using the Juspay profile
AI_PROFILE=juspay nix run github:juspay/agent-distro
AI_PROFILE=juspay AI_HARNESS=omp nix run github:juspay/agent-distro -- --version- Adds Juspay’s skills and Kolu.
- Gateway. Runs OMP, Pi, and OpenCode against Juspay’s LiteLLM gateway, prompting for
LITELLM_API_KEYunless it is exported.49AI_GATEWAY=0keeps the plugins but skips gateway initialization. - Own login. Codex and Claude Code always use their own login.
- MCP servers. Kolu’s MCP server needs
koluonPATH. The profile suppliesmcp-nixositself, at its latest release.
A profile is a directory under profiles/:
profiles/
registry.nix # { default = "<name>"; } — the profile the list opens on
<name>/profile.nix # { name; description; plugins; gateway; packages; }
<name>/npins/ # optional: the profile's pinned plugin and package sources
- Discovered. Profiles are discovered from the directory listing, so adding one is adding a directory; nothing in
flake.nixnames them. - Plain data.
profile.nixis a plain attrset whosenamemust match its directory.50 - Pinned with npins. Each profile pins its own sources, not with flake inputs:51
let sources = import ./npins;
in { name = "example"; plugins = [ sources.skills ]; /* … */ }- Adding a source.
npins --directory profiles/<name>/npins add github <owner> <repo>follows the repository’s releases; add--branch mainto follow a branch. - Updates. The daily update advances every profile’s pins alongside the harnesses: a release pin to the latest release, a branch pin to its head.
Harnesses
Each harness is a directory under harnesses/; its README holds the design notes for that adapter.
| Harness | Command | Notes |
|---|---|---|
| Oh My Pi | omp |
design notes |
| Codex | codex |
design notes |
| Claude Code | claude |
design notes |
| OpenCode | opencode |
design notes |
| OpenCode v2 | opencode2 |
design notes |
| Pi | pi |
design notes |
How it works
- Profile. Harness-independent data.
- Harness. The agent application.
- Plugin. A portable Agent Plugins directory.
- Gateway. An optional LiteLLM proxy used by OMP, Pi, and OpenCode.
Harness design notes live in each directory under harnesses/.
Runtime
- TypeScript, no build step. What runs on your machine beyond the harnesses themselves52 is TypeScript under src/, run by Node 24’s native type stripping: no
package.json, bundler or compile step.53 - Dependencies. Node comes from the distribution’s nixpkgs. Its one npm dependency is
yaml, for OMP’s config.54 - Entry points. The commands you run, and the shims Home Manager installs, stay small generated shell scripts that call into it.
Reading plugins
Every plugin is read once, harness-independently, against Agent Plugins 1.0.0, by the same reader and the same harness writers.55
- Invalid manifest. Fails the build (or the launch) with a message naming the field.
- Lesser problems. Skipped skills, disabled
mcp.jsonfiles and invalid server entries are reported in the build log (or on stderr).56 - Commands. A bare MCP
commandis found onPATH: the profile’spackagesfirst, then the user’s own. A./command runs from the plugin.
One known gap: Claude Code expands
${VAR}in a remote server’surlandheaders, which the spec forbids.57
Shared homes
Profiles share each harness’s own home directory, so what OMP and Codex persist outlives the profile that wrote it.58 Claude Code’s plugins last only for the session.
Development
nix build .#default # the picker, and through it every profile's launchers
nix build .#vanilla # one profile's bundle: its harnesses plus its own bin/agent-distro
nix flake check
just test # offline NixOS VM tests; Linux with KVM
just test-template
just demo # re-record doc/demo.gif and the website's stills
python3 .github/scripts/test-update-flake.pyThe TypeScript’s own checks need no VM or KVM, and nix flake check at the root does not run them. Build them from the test flake:
cd test && nix build --no-link .#checks.x86_64-linux.{reader,launch-plugins,omp-adapter,pi-adapter,opencode-adapter,update-schedule,list-json,picker-layout,picker-auth}| Check | Covers |
|---|---|
reader |
the plugin reader |
launch-plugins |
AGENT_DISTRO_PLUGINS: cache keys, re-translation, precedence and the launches it fails |
<harness>-adapter |
a harness’s tests/check-adapter.ts |
update-schedule |
the updater’s schedule and cache policy |
list-json |
--list --json against its type, the chooser’s menu and --list |
picker-layout |
the chooser’s cell widths, truncation and box at common terminal sizes |
picker-auth |
the auth probes over fixture homes and stores |
CI
- Daily sources. CI runs
.github/scripts/update-sources.sh,59 then updates the root and test locks. - Report. The update report reads resolved versions and each harness’s release-note metadata.
- Cache. CI pushes realised paths to the OSS cache.60
- Merge gate. The daily update PR merges only after the required Linux/macOS builds and VM/template checks pass.61
- Cancellation. Superseded PR runs are cancelled.
- Consumers. They run
nix flake update agent-distro.
For manual updates, run bash .github/scripts/update-sources.sh, nix flake update, and then bash test/update-lock.sh.
Test library
Consumers can import test/lib.nix { pkgs; launchers; profile; features; koluPlugin ? null; }. Checks are selected by required features from each harness’s metadata;62 picker is shared.
- Also
mkLaunchers. Rebuild and gateway checks also acceptmkLaunchers. - Plugin rebuilds. Select plugin rebuild checks only for nonempty skill plugins.
- Coverage. The test flake covers vanilla (with kolu’s plugin loaded at launch), Juspay, and spec fixtures, plus
registry(the picker over the whole profile registry).
Logo
doc/logo.svg is the source vector; downstream consumers (kolu) vendor a copy.
Keep it transparent, textless, readable at 16 px.
Adding a harness
Create harnesses/<name>/ with four files:
| File | Holds |
|---|---|
meta.nix |
title, order, auth,63 releaseNotes = version: URL, and checks |
default.nix |
the adapter, accepting { pkgs, plugins, gateway, package, profileName }64 |
source.nix |
{ pkgs } to package |
README.md |
design notes |
- Alongside. Pins in
npins/, scripts intests/, and an optional custom updater inupdate.py. - No registration. Nothing outside this directory needs registration.65
- Logic beyond shell. Keep it in
src/harness/<name>.tsand call it with(import ../../lib/runtime.nix pkgs).script "harness/<name>.ts".66 - Unit checks. Those that need no VM go in
tests/check-adapter.ts, which receives the runtime’ssrcand the harness directory. - Checks. Checks declare
{ name; script; requires ? []; packages ? []; env ? {}; diskSize ? null; }.67scriptis VM-driver Python.68 - Gateway checks. They receive the shared fake service and launchers configured against it.
- Fixtures.
mkLaunchersalso accepts asources = name: pkgs: …hook for package fixtures.
The cache is named in the flake’s
nixConfig. The Home Manager updater in Install handles the cache itself.↩︎The active profile’s name and description take a row of their own under the header. The panel shows the highlighted harness’s tagline and its auth status.↩︎
So the default profile opens, the remembered one is dotted, and switching to it lands on that harness.↩︎
Names unchanged for script compatibility.↩︎
Also usable in scripts and non-interactive shells.↩︎
Defaulting to the one
registry.nixnames.↩︎The profile otherwise defaults to the registry default.↩︎
By
agent-distro juspay, byAI_PROFILE, or by being a single-profile distribution.↩︎Shown without revision suffixes.↩︎
Presence only: the picker contacts no provider and checks no token’s validity or expiry.↩︎
One whose store cannot be read shows nothing at all, and no mark.↩︎
Default
~/.claude.json.↩︎Read from
tokens.id_tokenwithout checking its signature;ChatGPTwhen the claim is absent.↩︎The gateway and the harness’s own providers are usable at once.↩︎
OMP’s
~/.omp/agent/agent.db, Pi’s$PI_CODING_AGENT_DIR/auth.json(default~/.pi/agent/auth.json), OpenCode’s${XDG_DATA_HOME:-~/.local/share}/opencode/auth.json.↩︎↑/↓ still move, Backspace edits, and Enter takes the highlighted match.↩︎
TERMunset ordumb, no controlling terminal, or fewer rows or columns than the box needs.↩︎The cursor and filter are kept.↩︎
An unavailable state directory is silently ignored.↩︎
Switching to the remembered profile puts the cursor on that harness.↩︎
Its shape is stable, typed as
Listingin src/listing.ts.↩︎Without the package’s
+revision suffix.↩︎The plain
--listand the chooser read the same value, so the three cannot disagree.↩︎So a path containing
:cannot be given.↩︎A relative entry is resolved against the directory the launcher starts in, and symlinks are followed.
~is expanded by the shell, and only where an assignment expands it: unquoted, in bash or zsh, not in fish.↩︎Skipped skills, a disabled
mcp.jsonor an invalid server entry are reported on stderr, on every launch.↩︎No
versionis compared.↩︎The cache directory must be an absolute path. A launch never calls Nix, and a cache it cannot write fails it.↩︎
A plugin under
/nix/storeis keyed by its store path, as is, without reading it. Any other directory is keyed by a hash of its contents (its NAR serialization, computed without Nix, leaving out a top-level.git) and of its absolute path, so a key is particular to one machine and user. That directory is hashed in full on every launch: a large tree (anode_modules, say) costs launch time, and a FIFO or an unreadable file in it fails the launch.↩︎So old versions of an edited checkout and translations for an older agent-distro do not pile up.↩︎
Claude Code and OMP take them as arguments, OpenCode in its session config, and Codex through
-coverrides; Codex keeps an inert copy in its plugin cache, removed once unused for 14 days. Pi only reads its own files, so it records what a launch added and its next launch, whenever that is, takes it back.↩︎At 02:00, 08:00, 14:00 and 20:00 UTC, two hours after upstream’s update.↩︎
At activation its prune stage deletes sibling state directories left from an earlier flake/profile choice.↩︎
Never compiling; it passes nix only
substituters.↩︎It checks the UTC boundary only when you pass
--scheduled, as the module’s launchd does.↩︎The updater replaces
<state>/currentdirectly, so there is no out-link pruning for the consumer to do.↩︎{"progress":{"done":<bytes>,"total":<bytes>}}while nix fetches, then{"result":"updated","bundle":"/nix/store/…"}(orunchanged, orskipped/failedwith areason).↩︎cache.nixos.asia/oss, configurable withservices.agent-distro.substituters.↩︎Nothing more is needed when you are a trusted Nix user or run a single-user install. Otherwise the daemon ignores the updater’s request unless your system config already lists the cache.↩︎
A line such as
skipped: cache https://cache.nixos.asia/oss not usable; add it to nix.settings substituters/trusted-public-keys. The same line is not repeated, and a skip is not retried until the next scheduled run.↩︎Update and failure events, in your local timezone, e.g.
2026-10-01T23:03:17+05:30 juspay updated: Pi 0.99.2 → 1.0.0; unchanged runs add nothing.↩︎Each is a
plugin.jsonmanifest, with optionalskills/<name>/SKILL.mdandmcp.json.↩︎An MCP server that a plugin declares by bare command, such as
"command": "mcp-nixos", is found onPATH. List its package here and it is built, or fetched from a binary cache, together with the launcher, so the server starts at once instead of being downloaded when the agent first asks for it, and every user runs the version the build pinned.↩︎Every harness, the default picker, and the
<profile.name>bundle, which carries its own picker asbin/agent-distro.↩︎The Home Manager updater only installs what that cache holds and never compiles.↩︎
It preserves existing settings: choose personal models in OMP or Pi if you previously used gateway defaults. Codex and Claude Code always use their own login.↩︎
src/listing.ts types both files (
ProfileFile,parseProfileFile).↩︎Every function evaluates from a plain
importof itslib/*.nixfile with an arbitrary nixpkgspkgs; a required argument left out is an eval error naming it.↩︎Create a key at grid.ai.juspay.net/dashboard (Juspay VPN required).↩︎
packagesis its one function, since only the builder has a package set.↩︎The top-level
flake.lockis inherited by everyone who builds onlib.mkFlake, and no distribution’s plugins belong there.↩︎The plugin reader, each harness’s config writer, the picker and the Home Manager updater.↩︎
The one exception is the entry for
AGENT_DISTRO_PLUGINS, plain JavaScript so that it can turn on Node’s compile cache before any TypeScript loads.↩︎A tarball pinned in
lib/npins, which lib/runtime.nix links in asnode_modules/yaml.↩︎A profile’s at build time, and one from
AGENT_DISTRO_PLUGINSat launch, into the cache described in Plugins at launch.↩︎As the spec’s failure boundaries require.↩︎
Those values cannot go through a launcher, so a remote server whose URL or headers contain
${reaches Claude Code as is.↩︎A Codex marketplace registered by one profile is still registered when you launch
vanilla, and so are the model roles a gateway filled into OMP’s config.↩︎It discovers npins directories under
profiles/,harnesses/, andlib/, re-pinsyamlto the newest npm release of its major version, and runs each harness’supdate.py.↩︎ATTIC_TOKENis needed except on fork PRs.↩︎The update workflow approves runs GitHub holds back for automation-created pull requests and squash-merges after the checks required by
Require CI on mainpass.↩︎The features are
plugins,gateway,kolu,spec, andkoluLaunch, which loadskoluPluginthroughAGENT_DISTRO_PLUGINS.↩︎auth = "anthropic" | "openai" | "gateway".↩︎The explicit
profileNameargument supports profile-specific registration, such as Codex’s marketplace name.↩︎The picker, bundles, outputs, reserved names, checks, and daily report all discover it.↩︎
For an adapter that needs more than shell: translating plugin descriptions into the harness’s config, merging user state at launch.↩︎
Packages can name
updated,upstream,koluFixture, orrecordFixture.↩︎Use
import ../../test/guest-script.nix ./tests/check.pyto run a guest script as the unprivileged VM user.↩︎