diff options
Diffstat (limited to 'profiles')
| -rw-r--r-- | profiles/sandbox.nix | 51 |
1 files changed, 50 insertions, 1 deletions
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); }; } |
