- Nix 70.4%
- JavaScript 21.1%
- Shell 6.9%
- TypeScript 1.6%
| Filename | Latest commit message | Latest commit date |
|---|---|---|
| .claude | ||
| .forgejo/workflows | ||
| .trail | ||
| nix | ||
| providers | ||
| rules | ||
| skills | ||
| subagents | ||
| .editorconfig | ||
| .gitattributes | ||
| .gitignore | ||
| .mcp.json | ||
| AGENTS.md | ||
| alejandra.toml | ||
| CLAUDE.md | ||
| flake.lock | ||
| flake.nix | ||
| opencode.json | ||
| README.md | ||
| skills-lock.json | ||
| statix.toml | ||
agent-env
My whole coding-agent setup as one flake: a Home Manager module that installs the agents (Claude Code, codex, opencode, pi, their desktop apps and T3 Code) and gives every one of them the same skills, rules, MCP servers, plugins, subagents and model gateways.
Everything sits under m4rti.agents. apps decides which agents exist; every
other part is installed into whichever of those agents can take it, so turning
an app off never leaves half its configuration behind.
Use it
{
inputs.agent-env = {
url = "git+https://git.cumsek.com/Marti/agent-env";
inputs.nixpkgs.follows = "nixpkgs";
inputs.home-manager.follows = "home-manager";
};
# ... in a Home Manager configuration:
imports = [inputs.agent-env.homeManagerModules.default];
m4rti.agents = {
apps = {
claude.code.enable = true;
claude.desktop.enable = true;
gpt.codex.enable = true;
gpt.desktop.enable = true;
opencode.cli.enable = true;
opencode.desktop.enable = true;
pi.enable = true;
t3code.enable = true;
};
skills.enable = true;
rules.enable = true;
mcp.enable = true;
plugins.enable = true;
subagents.enable = true;
providers.cumsek = {
name = "Cumsek";
url = "https://ai.cumsek.com";
keyFile = osConfig.sops.secrets."cumsek/ai/m4rti-api-key".path;
};
defaultModel = {
provider = "cumsek";
model = "cumsek/ornith1.5:9b";
};
};
}
The agents come from llm-agents.nix,
so a consumer needs no llm-agents input of its own. The pin does not follow
nixpkgs on purpose: numtide's binary cache only hits for their nixpkgs
revision, so add https://cache.numtide.com (key
niks3.numtide.com-1:DTx8wZduET09hRmMtKdQDxNNthLQETkc/yaX7M4qK0g=) to your
substituters. With llm-agents' overlay applied, pkgs.llm-agents is used
instead. Every package option can be overridden, e.g.
apps.claude.code.package = pkgs.claude-code for the nixpkgs build.
The module imports zeit's Home Manager module for plugins.zeit, so drop any
import of inputs.zeit.homeModules.default of your own.
What goes where
| Part | Claude Code | codex | opencode | pi |
|---|---|---|---|---|
skills |
skills |
skills |
skills |
<configDir>/skills |
rules |
rulesDir |
AGENTS.md |
AGENTS.md |
AGENTS.md |
mcp |
mcpServers |
mcp_servers |
mcp |
mcp.json + pi-mcp-adapter |
subagents |
agents/*.md |
agents/*.toml |
agents/*.md |
agents/*.md + pi-subagents |
providers |
- | - | provider + discovery plugin | discovery extension |
plugins.ponytail |
plugin | plugin | plugin | - |
plugins.open-code-review |
/review plugin |
- | - | - |
plugins.zeit |
turn-tracking hooks | - | - | - |
plugins.trail-code |
MCP server | MCP server | MCP server | MCP server |
plugins.agent-browser |
CLI on PATH |
CLI on PATH |
CLI on PATH |
CLI on PATH |
The desktop apps (Claude Desktop, ChatGPT, opencode desktop) are only installed; they keep their own settings.
Options
| Option | Default | What it does |
|---|---|---|
apps.claude.code.enable |
false |
Claude Code, wrapped so hooks find node and python |
apps.claude.desktop.enable |
false |
Claude Desktop |
apps.gpt.codex.enable |
false |
The Codex CLI |
apps.gpt.desktop.enable |
false |
The ChatGPT desktop app (ChatGPT and Codex) |
apps.opencode.cli.enable |
false |
The opencode CLI |
apps.opencode.desktop.enable |
false |
The opencode desktop app |
apps.pi.enable |
false |
The pi coding agent |
apps.t3code.enable |
false |
T3 Code from the nightly channel, with forgejo-cli as an extra provider |
apps.<app>.package |
from llm-agents | The package to install |
skills.enable |
false |
The skills in skills/ |
skills.src |
skills/ |
The skills directory to install |
rules.enable |
false |
The global rules in rules/ |
rules.dir |
rules/ |
The rules directory to install |
mcp.enable |
false |
The MCP servers in mcp.servers |
mcp.servers.<name> |
cumsek |
An MCP server; see below |
plugins.enable |
false |
Every plugin below |
plugins.<name>.enable |
plugins.enable |
ponytail, trail-code, open-code-review, zeit, agent-browser |
subagents.enable |
false |
The subagents in subagents/ |
subagents.dir |
subagents/ |
The subagents directory to install |
providers.<id> |
{} |
A LiteLLM gateway: name, url, keyFile |
defaultModel |
null |
pi's default provider and model |
Every app setting this module makes (model, theme, telemetry off, ...) is a
mkDefault, so plain assignments override it.
MCP servers and secrets
A server is either local (command, args, env) or remote (url,
headers). Any env or headers value can be a file instead of a literal,
which is how sops-nix secrets get in without touching the store:
m4rti.agents.mcp.servers = {
forgejo = {
command = "forgejo-mcp";
args = ["--url" "https://git.cumsek.com"];
env.FORGEJO_ACCESS_TOKEN.file = osConfig.sops.secrets."cumsek/forgejo/token".path;
};
work = {
url = "https://mcp.example.com/mcp";
headers."X-Api-Key".file = osConfig.sops.secrets."work/mcp-key".path;
};
};
Each agent reads the file at startup through whatever it supports:
| Agent | Local server env |
Remote server headers |
|---|---|---|
| Claude Code | wrapper script | headersHelper script |
| codex | wrapper script | env_http_headers, exported by a codex wrapper |
| opencode | {file:...} |
{file:...} |
| pi | !cat <file> |
!cat <file> |
Servers follow mcp.enable unless they set enable themselves;
mcp.servers.cumsek.enable = false drops the built-in one. Home Manager's
programs.mcp is not used: it writes ~/.config/mcp/mcp.json, which pi's
adapter also reads, with {file:...} tokens pi does not understand.
Subagents
subagents/ holds one markdown file per subagent, written once and rendered
into each agent's format:
---
name: code-reviewer
description: One line the orchestrator matches against.
deny: edit, write, task
---
The system prompt.
deny takes edit, write, bash and task (spawning further subagents).
Claude Code gets them as disallowedTools, opencode as tools, pi as a
reduced tool allowlist, and codex as sandbox_mode = "read-only" when both
edit and write are denied. mode: primary makes the agent a primary one
in opencode; the others ignore it. nix flake check parses every file.
| Subagent | Role |
|---|---|
tech-lead |
Primary agent: scopes the request and delegates to the rest |
explorer |
Read-only recon that returns a map of files and symbols |
requirements-clarifier |
Turns a vague ask into acceptance criteria and questions |
architect |
Designs structural changes; writes no code |
implementer |
Builds a decided change in the style of the code around it |
test-writer |
Writes or fixes tests and runs them |
debugger |
Reproduces, finds the root cause, applies the smallest fix |
code-reviewer |
Reviews a diff for bugs, regressions and missing tests |
security-auditor |
Reports confirmed vulnerabilities with severity and fix |
docs-writer |
Brings README and docs in line with a change |
Plugins
- ponytail: ponytail's hooks
and commands. Its Claude and codex hooks call a bare
node, so the package pins them to a store Node. In codex, open/hooksonce and trust its two hooks. - trail-code: the
trailCLI (formerly graft, which still answers asgraft), andtrail mcpas thetrailMCP server in every agent. The per-repo hooks, statusline and learnings stay withtrail init.plugins.graftis a renamed alias. - open-code-review: the
ocrCLI and its Claude Code/reviewand/delegate-reviewcommands, from the same source. - zeit: the time tracker, with Claude Code turns tracked automatically.
- agent-browser: the CLI, pointed at a Nix-built Chromium because the
Chrome-for-Testing it downloads cannot run on NixOS. The skill that teaches
it ships with
skills.
Outputs
| Output | What it is |
|---|---|
homeManagerModules.default / .agents |
The module above |
packages.skills / packages.default |
The skills as a plain directory |
packages.ponytail |
The ponytail plugin, hooks pinned to a store Node |
checks |
Skill frontmatter, subagent parsing, a full module evaluation, alejandra --check, deadnix, statix |
devShells.default |
alejandra, deadnix, statix, trail |
formatter |
alejandra (tabs, see alejandra.toml) |
Systems: x86_64-linux, aarch64-linux, aarch64-darwin.
Adding things
- A skill:
skills/<name>/SKILL.md, opening with frontmatter that hasnameanddescription. The description is the whole trigger: say what the skill covers and when to reach for it. Long material goes in files next to it, linked fromSKILL.md. - A rule:
rules/<topic>.md. Every agent loads every rule on every turn, so keep them short. - A subagent:
subagents/<name>.mdin the format above. - An agent: an entry in
nix/module/apps.nix, a line innix/module/lib.nixenabledif it takes skills and the rest, and a column in the tables above.
Then nix flake check.
Layout
flake.nix outputs and checks
nix/
module/
default.nix imports the parts
lib.nix helpers shared by the parts
apps.nix the agents and their settings
skills.nix skills/ -> every agent
rules.nix rules/ -> every agent
mcp.nix MCP servers, secrets from files
plugins.nix ponytail, trail-code, open-code-review, zeit, agent-browser
subagents.nix subagents/ -> every agent
providers.nix LiteLLM gateways for opencode and pi
subagents.nix subagent parser and per-agent renderers
ponytail.nix the ponytail plugin package
skills/<name>/SKILL.md a skill
rules/<topic>.md a global rule
subagents/<name>.md a subagent
providers/ model discovery for the gateways
Credits
Frontend-Design comes from Anthropic's skills
and keeps its own license header. agent-browser is the discovery stub shipped
with the agent-browser CLI. The
rest are written for this repo.