summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorHenry J. Webster <hwebs@hwebs.info>2026-08-03 23:26:57 -0500
committerHenry J. Webster <hwebs@hwebs.info>2026-08-03 23:26:57 -0500
commit6f135470d274daa3e7a7fec1cb8f36204abd90f0 (patch)
treef3abeb75cabc6ebadf0ceaa525401987cb62cd51
parent9acf89a3af3fd4c354fcd887397b0c035bb21494 (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.md38
-rw-r--r--profiles/sandbox.nix51
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);
};
}