feat(workflow): add flake and just entrypoints

This commit is contained in:
Bryan Ramos 2026-04-02 13:26:08 -04:00
parent 511076e059
commit eda4748012
4 changed files with 323 additions and 25 deletions

View file

@ -1,4 +1,4 @@
# Global Claude Code Instructions
Rules are modularized in `~/.claude/rules/` and loaded automatically.
Rules are modularized in `rules/` and loaded automatically by the generated Claude config.
Agent-team specific protocols live in skills (orchestrate, conventions, worker-protocol, qa-checklist, message-schema, project).

149
README.md
View file

@ -1,22 +1,46 @@
# agent-team
A portable agent team configuration for Claude Code and Codex CLI. Clone it, run `./generate.sh` + `./install.sh`, and both tools get a full team of specialized subagents and shared skills on any machine.
A portable agent team configuration for Claude Code and Codex CLI. Clone it, run the flake entrypoints or the `just` wrapper, and both tools get a full team of specialized subagents and shared skills on any machine.
## Quick install
```bash
git clone <repo-url> ~/agent-team
cd ~/agent-team
git clone <repo-url>
cd agent-team
nix develop # enter devShell with yq + envsubst
./generate.sh # generate Claude + Codex config from templates
./install.sh # symlinks into ~/.claude/ and ~/.codex/ (if present)
nix run .#check # validate protocols + generate artifacts
nix run .#install # install into your Claude/Codex config dirs
```
`generate.sh` expands shared agent templates into tool-specific outputs for Claude Code and Codex CLI. `install.sh` symlinks the results into `~/.claude/` and `~/.codex/`. Works on Linux, macOS, and Windows (Git Bash).
The supported user-facing entrypoints are the flake apps and the `just` wrapper. `generate.sh` and `install.sh` remain the internal implementation layer behind them. Works on Linux, macOS, and Windows (Git Bash).
## Nix entrypoints
The flake exposes formal workflow entrypoints:
```bash
nix run .#validate # syntax + protocol presence/basic shape checks
nix run .#build # generate settings.json + claude/ + codex/
nix run .#check # validate + build
nix run .#install # run install.sh
nix flake check # run flake checks (validate + build in sandboxed check derivations)
```
`just` is also supported as a convenience wrapper over those same flake commands:
```bash
just validate
just build
just check
just install
just clean # removes generated artifacts: settings.json + claude/ + codex/
```
`generate.sh` and `install.sh` are kept as internal implementation details for portability and debugging, but they are no longer the primary documented workflow.
## Maintenance
**Symlink fragility:** `~/.claude/CLAUDE.md` and `~/.claude/settings.json` are installed as symlinks by `install.sh`. Some tools (including Claude Code itself when writing settings) resolve symlinks to regular files on write, silently breaking the link. If edits to the repo are no longer reflected in `~/.claude/`, re-run `./install.sh` to restore the symlinks.
**Symlink fragility:** the generated Claude files are installed as symlinks by `install.sh`. Some tools (including Claude Code itself when writing settings) resolve symlinks to regular files on write, silently breaking the link. If edits to the repo are no longer reflected in your Claude config dir, re-run `./install.sh` to restore the symlinks.
## Agents
@ -43,7 +67,7 @@ nix develop # enter devShell with yq + envsubst
## Rules
Global instructions are modularized in `rules/` and auto-loaded by Claude Code from `~/.claude/rules/` on every session. Each file covers a focused topic (git workflow, Nix preferences, response style, etc.). Agent-team specific protocols live in skills, not rules.
Global instructions are modularized in `rules/` and auto-loaded by Claude Code from the installed Claude config on every session. Each file covers a focused topic (git workflow, Nix preferences, response style, etc.). Agent-team specific protocols live in skills, not rules.
## How to use
@ -65,7 +89,7 @@ For simple tasks, invoke an agent directly:
### Codex CLI
Agents are available as named agents in `~/.codex/agents/`. Invoke them with:
Agents are available as named agents in the installed Codex config. Invoke them with:
```
codex --agent worker "Fix the broken pagination in the user list endpoint"
@ -73,17 +97,112 @@ codex --agent worker "Fix the broken pagination in the user list endpoint"
## Dual-target generation
Agent source files in `agents/` are the single source of truth. `generate.sh` derives tool-specific outputs for both Claude Code and [OpenAI Codex CLI](https://github.com/openai/codex).
This repo uses two authored protocol files:
- [SETTINGS.yaml](SETTINGS.yaml) for runtime policy (filesystem, approvals, network, model intent)
- [TEAM.yaml](TEAM.yaml) for team inventory metadata (agents, skills, rules)
Long-form instructions remain authored in Markdown (`agents/*.md`, `skills/*/SKILL.md`, `rules/*.md`).
Runtime policy is documented in [spec/agent-runtime-v1.md](spec/agent-runtime-v1.md) and described by [schemas/agent-runtime.schema.json](schemas/agent-runtime.schema.json). Team inventory is documented in [spec/team-protocol-v1.md](spec/team-protocol-v1.md). `generate.sh` derives tool-specific outputs for both Claude Code and [OpenAI Codex CLI](https://github.com/openai/codex).
### What gets generated
| Source | Generated | Location |
|---|---|---|
| `agents/*.md` (templates) | `claude/agents/*.md` | `~/.claude/agents/` |
| `agents/*.md` (templates) | `codex/agents/*.toml` | `~/.codex/agents/` |
| `rules/*.md` | `codex/AGENTS.md` | `~/.codex/AGENTS.md` |
| `settings.json` | `codex/config.toml` | `~/.codex/config.toml` |
| `skills/` | (shared as-is) | `~/.claude/skills/` + `~/.agents/skills/` |
| `TEAM.yaml` + `agents/*.md` | `claude/agents/*.md` | Claude config dir |
| `TEAM.yaml` + `agents/*.md` | `codex/agents/*.toml` | Codex config dir |
| `SETTINGS.yaml` | `settings.json` (compatibility artifact, generated) | repo root |
| `SETTINGS.yaml` | `claude/settings.json` | Claude config dir |
| `SETTINGS.yaml` | `codex/config.toml` | Codex config dir |
| `TEAM.yaml` + `rules/*.md` | `codex/AGENTS.md` | Codex config dir |
| `TEAM.yaml` + `skills/*/SKILL.md` | installed skill dirs | installed skill dirs |
All final config files are generated artifacts. The authored protocol sources are `SETTINGS.yaml`, `TEAM.yaml`, and Markdown instruction content. The primary workflows are `nix run .#build` / `nix run .#install` or the equivalent `just` commands.
Narrow compatibility caveats:
- TEAM schema is intentionally rigid/repo-specific in v1. Inventory changes require schema updates in lockstep.
- Claude generated agent frontmatter is normalized by generator serialization (field order/quoting), which may produce non-semantic diffs.
- Codex skill installation is TEAM-authoritative when `TEAM.yaml` is present. Legacy directory fallback is used only when TEAM is absent or unparseable.
Shared runtime intent is generated conservatively across tools:
| Shared source | Claude Code | Codex CLI |
|---|---|---|
| `runtime.filesystem = read-only` | `permissions.defaultMode = "plan"` | `sandbox_mode = "read-only"` |
| `runtime.filesystem = workspace-write` | `permissions.defaultMode = "acceptEdits"` | `sandbox_mode = "workspace-write"` |
| `runtime.approval = manual` | partially represented | `approval_policy = "on-request"` |
| `runtime.approval = guarded-auto` | partially represented | `approval_policy = "untrusted"` |
| `runtime.approval = full-auto` | partially represented | `approval_policy = "never"` |
Codex does not support Claude's per-tool `allow` / `deny` / `ask` patterns directly. The shared protocol keeps the intent portable, then adapters derive the closest target behavior. Use target-specific fields only where there is no shared equivalent:
```yaml
targets:
codex:
approval_policy: untrusted
network_access: false
claude:
claude_md_excludes:
- .claude/agent-memory/**
```
## Shared protocol
The protocol source is YAML because it is easier to read and annotate than JSON or TOML while still being easy to validate with JSON Schema.
- Runtime policy: [SETTINGS.yaml](SETTINGS.yaml)
- Runtime schema: [schemas/agent-runtime.schema.json](schemas/agent-runtime.schema.json)
- Runtime spec: [spec/agent-runtime-v1.md](spec/agent-runtime-v1.md)
- Team/inventory spec: [spec/team-protocol-v1.md](spec/team-protocol-v1.md)
The protocol is intentionally small in v1:
- portable model tier and reasoning level
- filesystem access intent
- approval intent
- network access
- portable tool classes
- protected paths
- dangerous shell command prompts
- limited target-specific escape hatches
Example:
```yaml
version: 1
model:
class: balanced
reasoning: medium
runtime:
filesystem: workspace-write
approval: guarded-auto
network_access: false
tools:
- shell
- read
- edit
- write
- glob
- grep
- web_fetch
- web_search
safety:
protected_paths:
- ~/.ssh/**
- ~/.aws/**
- ~/.gnupg/**
- "**/.env*"
dangerous_shell_commands:
ask:
- rm *
- git reset --hard*
- sudo *
```
## Model mapping

176
flake.nix
View file

@ -1,18 +1,176 @@
{
inputs.nixpkgs.url = "github:NixOS/nixpkgs/nixpkgs-unstable";
outputs = { nixpkgs, ... }:
outputs = { self, nixpkgs, ... }:
let
systems = [ "x86_64-linux" "aarch64-linux" "x86_64-darwin" "aarch64-darwin" ];
forAllSystems = f: nixpkgs.lib.genAttrs systems (system: f nixpkgs.legacyPackages.${system});
in {
in
{
devShells = forAllSystems (pkgs: {
default = pkgs.mkShell {
packages = with pkgs; [
yq-go
gettext
codex
];
};
default = pkgs.mkShell {
packages = with pkgs; [
yq-go
gettext
just
];
};
});
apps = forAllSystems (pkgs: let
pythonEnv = pkgs.python3.withPackages (ps: with ps; [ pyyaml jsonschema ]);
runtimeInputs = with pkgs; [
bash
yq-go
gettext
pythonEnv
];
bashBin = "${pkgs.bash}/bin/bash";
validateCmd = ''
# Script syntax checks
${bashBin} -n ./generate.sh
${bashBin} -n ./install.sh
# Protocol file presence checks
test -f ./SETTINGS.yaml
test -f ./TEAM.yaml
test -f ./schemas/agent-runtime.schema.json
test -f ./schemas/team.schema.json
# Basic protocol shape checks
yq -e '.version == 1' ./SETTINGS.yaml
yq -e '.version == 1' ./TEAM.yaml
yq -e '.agents.order | type == "!!seq"' ./TEAM.yaml
yq -e '.skills.order | type == "!!seq"' ./TEAM.yaml
yq -e '.rules.order | type == "!!seq"' ./TEAM.yaml
# JSON Schema validation for protocol files
python <<'PY'
import json
from pathlib import Path
import yaml
from jsonschema import validate
root = Path(".")
settings_data = yaml.safe_load((root / "SETTINGS.yaml").read_text())
team_data = yaml.safe_load((root / "TEAM.yaml").read_text())
settings_schema = json.loads((root / "schemas/agent-runtime.schema.json").read_text())
team_schema = json.loads((root / "schemas/team.schema.json").read_text())
validate(instance=settings_data, schema=settings_schema)
validate(instance=team_data, schema=team_schema)
PY
'';
mkAppScript = name: text:
pkgs.writeShellApplication {
inherit name runtimeInputs text;
};
in {
build = {
type = "app";
program = "${mkAppScript "build" ''
set -euo pipefail
test -f ./generate.sh || { echo "Run this command from the repository root."; exit 1; }
${bashBin} ./generate.sh
''}/bin/build";
meta.description = "Generate Claude and Codex build artifacts from the authored protocol files.";
};
validate = {
type = "app";
program = "${mkAppScript "validate" ''
set -euo pipefail
test -f ./generate.sh || { echo "Run this command from the repository root."; exit 1; }
${validateCmd}
''}/bin/validate";
meta.description = "Validate scripts and protocol files.";
};
check = {
type = "app";
program = "${mkAppScript "check" ''
set -euo pipefail
test -f ./generate.sh || { echo "Run this command from the repository root."; exit 1; }
${validateCmd}
${bashBin} ./generate.sh
''}/bin/check";
meta.description = "Run validation and generation together.";
};
install = {
type = "app";
program = "${mkAppScript "install" ''
set -euo pipefail
test -f ./install.sh || { echo "Run this command from the repository root."; exit 1; }
${bashBin} ./install.sh
''}/bin/install";
meta.description = "Install generated artifacts into Claude and Codex config directories.";
};
});
checks = forAllSystems (pkgs: let
pythonEnv = pkgs.python3.withPackages (ps: with ps; [ pyyaml jsonschema ]);
runtimeInputs = with pkgs; [
bash
yq-go
gettext
pythonEnv
];
bashBin = "${pkgs.bash}/bin/bash";
validateCmd = ''
${bashBin} -n ./generate.sh
${bashBin} -n ./install.sh
test -f ./SETTINGS.yaml
test -f ./TEAM.yaml
test -f ./schemas/agent-runtime.schema.json
test -f ./schemas/team.schema.json
yq -e '.version == 1' ./SETTINGS.yaml
yq -e '.version == 1' ./TEAM.yaml
yq -e '.agents.order | type == "!!seq"' ./TEAM.yaml
yq -e '.skills.order | type == "!!seq"' ./TEAM.yaml
yq -e '.rules.order | type == "!!seq"' ./TEAM.yaml
python <<'PY'
import json
from pathlib import Path
import yaml
from jsonschema import validate
root = Path(".")
settings_data = yaml.safe_load((root / "SETTINGS.yaml").read_text())
team_data = yaml.safe_load((root / "TEAM.yaml").read_text())
settings_schema = json.loads((root / "schemas/agent-runtime.schema.json").read_text())
team_schema = json.loads((root / "schemas/team.schema.json").read_text())
validate(instance=settings_data, schema=settings_schema)
validate(instance=team_data, schema=team_schema)
PY
'';
mkCheck = name: text:
pkgs.runCommand name { nativeBuildInputs = runtimeInputs; src = ./.; } ''
mkdir -p "$TMPDIR/repo"
cp -R "$src"/. "$TMPDIR/repo"
chmod -R u+w "$TMPDIR/repo"
cd "$TMPDIR/repo"
${text}
touch "$out"
'';
in {
validate = mkCheck "agent-team-validate-check" ''
set -euxo pipefail
${validateCmd}
'';
build = mkCheck "agent-team-build-check" ''
set -euxo pipefail
${validateCmd}
${bashBin} ./generate.sh
'';
});
};
}

21
justfile Normal file
View file

@ -0,0 +1,21 @@
set shell := ["bash", "-eu", "-o", "pipefail", "-c"]
default: help
help:
@just --list
validate:
nix run .#validate
build:
nix run .#build
check:
nix run .#check
install:
nix run .#install
clean:
rm -rf settings.json claude codex