diff options
| author | Henry J. Webster <hwebs@hwebs.info> | 2026-08-03 23:26:57 -0500 |
|---|---|---|
| committer | Henry J. Webster <hwebs@hwebs.info> | 2026-08-03 23:26:57 -0500 |
| commit | 6f135470d274daa3e7a7fec1cb8f36204abd90f0 (patch) | |
| tree | f3abeb75cabc6ebadf0ceaa525401987cb62cd51 | |
| parent | 9acf89a3af3fd4c354fcd887397b0c035bb21494 (diff) | |
profiles: add agent-context — one AGENTS.md for every harness
There is no cross-vendor standard for agent instruction files: claude-code
reads CLAUDE.md, opencode and pi read AGENTS.md, each with its own global
path. Keep one canonical file (common/AGENTS.md) in this repo, ship it to
/etc/AGENTS.md, and symlink every harness's global context path to it via
tmpfiles for all normal users:
~/.claude/CLAUDE.md (claude-code 2.1.187 — no AGENTS.md support)
~/.config/opencode/AGENTS.md (opencode 1.15.10)
~/.pi/agent/AGENTS.md (pi 0.75.4)
Paths verified against the packaged binaries; see the profile header for
details and for the per-repo convention (AGENTS.md + CLAUDE.md symlink).
Assisted-by: claude-code:Fable 5
| -rw-r--r-- | common/AGENTS.md | 38 | ||||
| -rw-r--r-- | profiles/sandbox.nix | 51 |
2 files changed, 88 insertions, 1 deletions
diff --git a/common/AGENTS.md b/common/AGENTS.md new file mode 100644 index 0000000..24690c0 --- /dev/null +++ b/common/AGENTS.md @@ -0,0 +1,38 @@ +# Global agent context (all harnesses, all machines) + +One file, every harness: this is `common/AGENTS.md` in the nixos-config repo, +deployed by `profiles/sandbox.nix` to `/etc/AGENTS.md` and symlinked to +each harness's global context path (`~/.claude/CLAUDE.md`, +`~/.config/opencode/AGENTS.md`, `~/.pi/agent/AGENTS.md`). To change it, edit it +in nixos-config and rebuild — it is read-only everywhere else on purpose. + +## Machines + +- `kusanagi` — AMD workstation (ROCm GPU; Ollama/Immich/Kavita services). +- `enzo` — laptop. + +Both are NixOS, configured by the flake in `nixos-config`. + +## If you are running as `yokai` (the agent sandbox) + +- You work in your own clone of each repo under `/srv/git/<repo>`; the owner's + canonical repo lives in their home, which you cannot reach. +- Deliver work as commits in your clone — the owner reviews by fetching from + it, so uncommitted work is invisible to them. +- Never push to origin or any external remote, and never sign commits; the + owner squashes and signs what lands. Full workflow: `profiles/sandbox.nix`. + +## Conventions + +- Nix code: format with `nixfmt`. Profiles stay username-agnostic — per-user + configuration goes in `machines/*/default.nix`. +- Never run `nixos-rebuild` or otherwise apply system configuration; propose + changes as commits and let the owner apply them. + +### Git + +- Trailers: `Assisted-by: AGENT_NAME:MODEL_VERSION`. Do NOT use `Co-authored-by`. +- Commit subjects: `area: imperative summary` (e.g. `profiles: extract shared + sandbox profile`); the body explains why, not what. + + diff --git a/profiles/sandbox.nix b/profiles/sandbox.nix index 40b962d..f55b579 100644 --- a/profiles/sandbox.nix +++ b/profiles/sandbox.nix @@ -75,6 +75,49 @@ let owner = config.sandbox.owner; + + # --- One instructions file for every coding-agent harness --- + # There is no cross-vendor standard for "the markdown the agent reads first": + # claude-code wants CLAUDE.md, opencode and pi want AGENTS.md, each with its + # own global path. Rather than maintaining N diverging copies, + # common/AGENTS.md is the single source of truth: it ships to /etc/AGENTS.md + # (below), and every harness's global context path becomes a symlink to it. + # Edit the file in this repo, rebuild, and every harness for every user sees + # the same text. + # + # Global paths, verified against the packaged harnesses (2026-08): + # claude-code 2.1.187: reads ~/.claude/CLAUDE.md ("user memory") + CLAUDE.md + # up the project tree. Does NOT read AGENTS.md (the only mentions in the + # binary are in the /init prompt), hence the CLAUDE.md-named symlink. + # opencode 1.15.10: reads ~/.config/opencode/AGENTS.md + project AGENTS.md. + # pi 0.75.4: reads <agentDir>/AGENTS.md or CLAUDE.md (agentDir defaults to + # ~/.pi/agent — dist/config.js), plus AGENTS.md|CLAUDE.md walking up from + # cwd (dist/core/resource-loader.js). AGENTS.md wins over CLAUDE.md. + # + # Per-repo instructions are a separate layer and stay in each repo: commit an + # AGENTS.md at the repo root plus a `CLAUDE.md -> AGENTS.md` symlink (git + # stores symlinks fine) and all three harnesses pick it up as project context. + # + # Semantics to be aware of: + # - L+ replaces whatever already sits at the path — that is the point + # (convergence), but salvage any hand-written ~/.claude/CLAUDE.md into + # common/AGENTS.md before the first rebuild on a machine. + # - The link resolves into the store, so in-band memory editing (claude's + # `#` shortcut / /memory) fails read-only: edits belong in this repo. + # - `d` lines apply mode+ownership to existing dirs too; the harness + # dot-dirs are normalized to 0700 (they hold credentials). + agentsTarget = "/etc/AGENTS.md"; + agentUsers = lib.filterAttrs (_: u: u.isNormalUser) config.users.users; + agentContextRulesFor = u: [ + "d ${u.home}/.claude 0700 ${u.name} ${u.group} -" + "L+ ${u.home}/.claude/CLAUDE.md - - - - ${agentsTarget}" + "d ${u.home}/.config 0700 ${u.name} ${u.group} -" + "d ${u.home}/.config/opencode 0700 ${u.name} ${u.group} -" + "L+ ${u.home}/.config/opencode/AGENTS.md - - - - ${agentsTarget}" + "d ${u.home}/.pi 0700 ${u.name} ${u.group} -" + "d ${u.home}/.pi/agent 0700 ${u.name} ${u.group} -" + "L+ ${u.home}/.pi/agent/AGENTS.md - - - - ${agentsTarget}" + ]; in { options.sandbox.owner = lib.mkOption { @@ -123,13 +166,19 @@ in ]; }; + # Canonical agent instructions, shared by all harnesses (see the big + # comment in the let-block above). + environment.etc."AGENTS.md".source = ../common/AGENTS.md; + # Drop dir for the agent's local clones. Created at activation as 2770 # root:ikai — setgid (new entries inherit ikai) + group-writable so the # owner and yokai can each place clones here without sudo; world sees # nothing. Sits outside ~ so /home/<owner> stays a sealed 700. Trailing "-" # = no age-cleaning. Each clone's own ownership is set when seeded (above). + # Plus the per-user agent-context symlinks from above. systemd.tmpfiles.rules = [ "d /srv/git 2770 root ikai -" - ]; + ] + ++ lib.concatMap agentContextRulesFor (lib.attrValues agentUsers); }; } |
